Initial commit - Typo3 11.5.41

This commit is contained in:
Matteo Gallo
2026-07-03 17:53:31 +02:00
commit 5ca4743197
6811 changed files with 568848 additions and 0 deletions
@@ -0,0 +1,40 @@
<?php
namespace GeorgRinger\News\ViewHelpers\Be;
/**
* 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 TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
/**
* Check if the checkbox should be active or not
*/
class IsCheckboxActiveViewHelper extends AbstractViewHelper
{
/**
* @var bool
*/
protected $escapeOutput = false;
/**
* Initialize arguments.
*/
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('id', 'int', 'Category id', true);
$this->registerArgument('categories', 'array', 'List of categories', false, []);
}
/**
* @return string
*/
public function render(): string
{
return (isset($this->arguments['categories']) && is_array($this->arguments['categories']) && in_array($this->arguments['id'], $this->arguments['categories'])) ? 'checked="checked"' : '';
}
}
@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace GeorgRinger\News\ViewHelpers\Category;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInterface;
/**
* 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.
*/
/**
* Get usage count. This ViewHelper retrieves a simple count and does *not* take any additional constraints into account!
* If you need additional constraints like startingpoint, archive, ... duplicate the ViewHelper and implement it on your own!
*
* Example usage
* {n:category.count(categoryUid:category.item.uid) -> f:variable(name: 'categoryUsageCount')}
* {categoryUsageCount}
*/
class CountViewHelper extends AbstractViewHelper implements ViewHelperInterface
{
use CompileWithRenderStatic;
/**
* Initialize arguments
*/
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('categoryUid', 'int', 'Uid of the category', true);
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
* @return int
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
): int {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_news_domain_model_news');
$categoryUid = $arguments['categoryUid'];
$languageUid = GeneralUtility::makeInstance(Context::class)->getAspect('language')->getId();
$count = $queryBuilder
->count('tx_news_domain_model_news.title')
->from('tx_news_domain_model_news')
->rightJoin(
'tx_news_domain_model_news',
'sys_category_record_mm',
'sys_category_record_mm',
$queryBuilder->expr()->eq('tx_news_domain_model_news.uid', $queryBuilder->quoteIdentifier('sys_category_record_mm.uid_foreign'))
)
->rightJoin(
'sys_category_record_mm',
'sys_category',
'sys_category',
$queryBuilder->expr()->eq('sys_category.uid', $queryBuilder->quoteIdentifier('sys_category_record_mm.uid_local'))
)
->where(
$queryBuilder->expr()->andX(
$queryBuilder->expr()->eq(
'sys_category.uid',
$queryBuilder->createNamedParameter($categoryUid, \PDO::PARAM_INT)
),
$queryBuilder->expr()->eq(
'sys_category_record_mm.tablenames',
$queryBuilder->createNamedParameter('tx_news_domain_model_news', \PDO::PARAM_STR)
),
$queryBuilder->expr()->eq(
'sys_category_record_mm.fieldname',
$queryBuilder->createNamedParameter('categories', \PDO::PARAM_STR)
),
$queryBuilder->expr()->in(
'tx_news_domain_model_news.sys_language_uid',
$queryBuilder->createNamedParameter([-1, $languageUid], Connection::PARAM_INT_ARRAY)
)
)
)
->execute()
->fetchColumn(0);
return $count;
}
}
@@ -0,0 +1,55 @@
<?php
namespace GeorgRinger\News\ViewHelpers\Check;
/**
* 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 GeorgRinger\News\Seo\NewsAvailability;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractConditionViewHelper;
/**
* Check if current news page is available
*/
class PageAvailableInLanguageViewHelper extends AbstractConditionViewHelper
{
/**
* Initialize additional argument
*/
public function initializeArguments()
{
$this->registerArgument('language', 'int', 'Language ot check', true);
parent::initializeArguments();
}
/**
* @param array|null $arguments
* @return bool
*/
protected static function evaluateCondition($arguments = null): bool
{
try {
$newsAvailabilityChecker = GeneralUtility::makeInstance(NewsAvailability::class);
return $newsAvailabilityChecker->check((int)$arguments['language']);
} catch (\UnexpectedValueException $e) {
return true;
}
}
/**
* @return mixed
*/
public function render()
{
if (static::evaluateCondition($this->arguments)) {
return $this->renderThenChild();
}
return $this->renderElseChild();
}
}
@@ -0,0 +1,68 @@
<?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 TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInterface;
/**
* ViewHelper to exclude news items in other plugins
*
* # Example: Basic example
*
* <code>
* <n:excludeDisplayedNews newsItem="{newsItem}" />
* </code>
* <output>
* None
* </output>
*/
class ExcludeDisplayedNewsViewHelper extends AbstractViewHelper implements ViewHelperInterface
{
use CompileWithRenderStatic;
/**
* Initialize arguments
*/
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('newsItem', News::class, 'news item', true);
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
*
* @return void
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
$newsItem = $arguments['newsItem'];
$uid = $newsItem->getUid();
if (empty($GLOBALS['EXT']['news']['alreadyDisplayed'])) {
$GLOBALS['EXT']['news']['alreadyDisplayed'] = [];
}
$GLOBALS['EXT']['news']['alreadyDisplayed'][$uid] = $uid;
// Add localized uid as well
$originalUid = (int)$newsItem->_getProperty('_localizedUid');
if ($originalUid > 0) {
$GLOBALS['EXT']['news']['alreadyDisplayed'][$originalUid] = $originalUid;
}
}
}
@@ -0,0 +1,48 @@
<?php
namespace GeorgRinger\News\ViewHelpers;
/**
* 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\ExtensionManagementUtility;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractConditionViewHelper;
/**
* Class ExtensionLoadedViewHelper
*/
class ExtensionLoadedViewHelper extends AbstractConditionViewHelper
{
/**
* Initialize additional argument
*/
public function initializeArguments()
{
$this->registerArgument('extensionKey', 'string', 'Extension which must be checked', true);
parent::initializeArguments();
}
/**
* @param array|null $arguments
* @return bool
*/
protected static function evaluateCondition($arguments = null): bool
{
return ExtensionManagementUtility::isLoaded($arguments['extensionKey']);
}
/**
* @return mixed
*/
public function render()
{
if (static::evaluateCondition($this->arguments)) {
return $this->renderThenChild();
}
return $this->renderElseChild();
}
}
@@ -0,0 +1,43 @@
<?php
namespace GeorgRinger\News\ViewHelpers\Format;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
/**
* 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.
*/
/**
* ViewHelper to render children which don't print out any actual content
*
* # Example: Basic example
* <code>
* <n:format.nothing>
* <n:titleTag>{newsItem.title}</n:titleTag>
* Fobar
* </n:format.nothing>
* </code>
* <output>
* nothing
* </output>
*/
class NothingViewHelper extends AbstractViewHelper
{
/**
* @var bool
*/
protected $escapeOutput = false;
/**
* Render children but do nothing else
*
* @return void
*/
public function render(): void
{
$this->renderChildren();
}
}
@@ -0,0 +1,52 @@
<?php
namespace GeorgRinger\News\ViewHelpers;
/**
* 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\Page\PageRenderer;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
/**
* ViewHelper to render data in <head> section of website
*
* # Example: Basic example
* <code>
* <n:headerData>
* <link rel="alternate"
* type="application/rss+xml"
* title="RSS 2.0"
* href="{f:uri.page(pageType: settings.list.rss.channel.typeNum)}" />
* </n:headerData>
* </code>
* <output>
* Added to the header: <link rel="alternate" type="application/rss+xml" title="RSS 2.0" href="uri to this page and type 9818" />
* </output>
*/
class HeaderDataViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
*
* @return void
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
$pageRenderer->addHeaderData($renderChildrenClosure());
}
}
@@ -0,0 +1,54 @@
<?php
namespace GeorgRinger\News\ViewHelpers;
/**
* 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\AbstractConditionViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInterface;
/**
* ViewHelper to check if the current news item is rendered as single view on the same page
*
* # Example: Basic example
* <code>
* {n:ifIsActive(newsItem: newsItem, then: 'active', else: '')}
* </code>
* <output>
* Renders the string "active" if the current news item is active
* </output>
*/
class IfIsActiveViewHelper extends AbstractConditionViewHelper implements ViewHelperInterface
{
public function initializeArguments()
{
$this->registerArgument('newsItem', 'object', 'News item', false);
parent::initializeArguments();
}
/**
* @param array|null $arguments
* @return bool
*/
protected static function evaluateCondition($arguments = null): bool
{
$vars = GeneralUtility::_GET('tx_news_pi1');
return isset($vars['news']) && isset($arguments['newsItem']) && (int)$arguments['newsItem']->getUid() === (int)$vars['news'];
}
/**
* @return mixed
*/
public function render()
{
if (static::evaluateCondition($this->arguments)) {
return $this->renderThenChild();
}
return $this->renderElseChild();
}
}
@@ -0,0 +1,82 @@
<?php
namespace GeorgRinger\News\ViewHelpers;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Page\AssetCollector;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
/**
* 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.
*/
/**
* Class ImageSizeViewHelper
*/
class ImageSizeViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;
/**
* Initialize arguments
*/
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('property', 'string', 'either width or height', true);
$this->registerArgument('image', 'string', 'generated image', true);
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
* @return int
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
): int {
$value = 0;
$typo3VersionNumber = VersionNumberUtility::convertVersionNumberToInteger(
VersionNumberUtility::getNumericTypo3Version()
);
// If TYPO3 version is previous version 11
if ($typo3VersionNumber < 11000000) {
$usedImage = trim($arguments['image'], '/');
} else {
$usedImage = trim($arguments['image']);
}
$assetCollector = GeneralUtility::makeInstance(AssetCollector::class);
$imagesOnPage = $assetCollector->getMedia();
if (isset($imagesOnPage[$usedImage])) {
switch ($arguments['property']) {
case 'width':
$value = $imagesOnPage[$usedImage][0];
break;
case 'height':
$value = $imagesOnPage[$usedImage][1];
break;
case 'size':
$file = Environment::getPublicPath() . '/' . $usedImage;
if (is_file($file)) {
$value = @filesize($file);
}
}
}
return $value;
}
}
@@ -0,0 +1,88 @@
<?php
namespace GeorgRinger\News\ViewHelpers;
use TYPO3\CMS\Core\Page\PageRenderer;
/**
* 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 TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInterface;
/**
* ViewHelper to include a css/js file
*
* # Example: Basic example
* <code>
* <n:includeFile path="{settings.cssFile}" />
* </code>
* <output>
* This will include the file provided by {settings} in the header
* </output>
*/
class IncludeFileViewHelper extends AbstractViewHelper implements ViewHelperInterface
{
use CompileWithRenderStatic;
public function initializeArguments()
{
$this->registerArgument('path', 'string', 'Path to the CSS/JS file which should be included', true);
$this->registerArgument('compress', 'bool', 'Define if file should be compressed', false, false);
$this->registerArgument('footer', 'bool', 'Define if JS file should be loaded in the footer', false, false);
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
*
* @return void
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
$path = $arguments['path'];
$compress = (bool)$arguments['compress'];
$footer = (bool)$arguments['footer'];
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
if (TYPO3_MODE === 'FE') {
$sanitizer = GeneralUtility::makeInstance(FilePathSanitizer::class);
try {
$path = $sanitizer->sanitize($path);
// JS
if (strtolower(substr($path, -3)) === '.js') {
if ($footer) {
$pageRenderer->addJsFooterFile($path, null, $compress, false, '', true);
} else {
$pageRenderer->addJsFile($path, null, $compress, false, '', true);
}
// CSS
} elseif (strtolower(substr($path, -4)) === '.css') {
$pageRenderer->addCssFile($path, 'stylesheet', 'all', '', $compress, false, '', true);
}
} catch (\Exception $e) {
// do nothing (todo handle properly?)
}
} else {
// JS
if (strtolower(substr($path, -3)) === '.js') {
$pageRenderer->addJsFile($path, null, $compress, false, '', true);
// CSS
} elseif (strtolower(substr($path, -4)) === '.css') {
$pageRenderer->addCssFile($path, 'stylesheet', 'all', '', $compress, false, '', true);
}
}
}
}
@@ -0,0 +1,211 @@
<?php
declare(strict_types=1);
namespace GeorgRinger\News\ViewHelpers\Iterator;
/**
* 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 TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\Variables\VariableProviderInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
/**
* Creates chunks from an input Array/Traversable with option to allocate items to a fixed number of chunks
*
* thanks to Claus Due and the EXT:vhs where this has been copied from
*/
class ChunkViewHelper extends AbstractViewHelper
{
/**
* @var bool
*/
protected $escapeChildren = false;
/**
* @var bool
*/
protected $escapeOutput = false;
public function initializeArguments()
{
$this->registerArgument('subject', 'mixed', 'The subject Traversable/Array instance to shift');
$this->registerArgument('count', 'integer', 'Number of items/chunk or if fixed then number of chunks', true);
$this->registerArgument('as', 'string', 'Template variable name to assign; if not specified the ViewHelper returns the variable instead.');
$this->registerArgument(
'fixed',
'boolean',
'If true, creates $count chunks instead of $count values per chunk',
false,
false
);
$this->registerArgument(
'preserveKeys',
'boolean',
'If set to true, the original array keys will be preserved',
false,
false
);
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
* @return array|mixed
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
$count = (int)$arguments['count'];
$fixed = (bool)($arguments['fixed'] ?? false);
$preserveKeys = (bool)($arguments['preserveKeys'] ?? false);
$subject = static::arrayFromArrayOrTraversableOrCSVStatic(
empty($arguments['as']) ? ($arguments['subject'] ?? $renderChildrenClosure()) : $arguments['subject'],
$preserveKeys
);
$output = [];
if (0 >= $count) {
return $output;
}
if ($fixed) {
$subjectSize = count($subject);
if (0 < $subjectSize) {
$chunkSize = (int)ceil($subjectSize / $count);
$output = array_chunk($subject, $chunkSize, $preserveKeys);
}
// Fill the resulting array with empty items to get the desired element count
$elementCount = count($output);
if ($elementCount < $count) {
$output += array_fill($elementCount, $count - $elementCount, null);
}
} else {
$output = array_chunk($subject, $count, $preserveKeys);
}
return static::renderChildrenWithVariableOrReturnInputStatic(
$output,
$arguments['as'],
$renderingContext,
$renderChildrenClosure
);
}
/**
* @param mixed $candidate
* @param bool $useKeys
*
* @return array
* @throws Exception
*/
protected static function arrayFromArrayOrTraversableOrCSVStatic($candidate, $useKeys = true): array
{
if ($candidate instanceof \Traversable) {
return iterator_to_array($candidate, $useKeys);
}
if ($candidate instanceof QueryResultInterface) {
/** @var QueryResultInterface $candidate */
return $candidate->toArray();
}
if (is_string($candidate)) {
return GeneralUtility::trimExplode(',', $candidate, true);
}
if (is_array($candidate)) {
return $candidate;
}
throw new Exception('Unsupported input type; cannot convert to array!', 1588049231);
}
/**
* @param mixed $variable
* @param string $as
* @param \TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface $renderingContext
* @param \Closure $renderChildrenClosure
* @return mixed
*/
protected static function renderChildrenWithVariableOrReturnInputStatic(
$variable,
$as,
RenderingContextInterface $renderingContext,
\Closure $renderChildrenClosure
) {
if (true === empty($as)) {
return $variable;
}
$variables = [$as => $variable];
$content = static::renderChildrenWithVariablesStatic(
$variables,
$renderingContext->getVariableProvider(),
$renderChildrenClosure
);
return $content;
}
/**
* Renders tag content of ViewHelper and inserts variables
* in $variables into $variableContainer while keeping backups
* of each existing variable, restoring it after rendering.
* Returns the output of the renderChildren() method on $viewHelper.
*
* @param array $variables
* @param VariableProviderInterface $templateVariableContainer
* @param \Closure $renderChildrenClosure
* @return mixed
*/
protected static function renderChildrenWithVariablesStatic(
array $variables,
$templateVariableContainer,
$renderChildrenClosure
) {
$backups = static::backupVariables($variables, $templateVariableContainer);
$content = $renderChildrenClosure();
static::restoreVariables($variables, $backups, $templateVariableContainer);
return $content;
}
/**
* @param array $variables
* @param VariableProviderInterface $templateVariableContainer
* @return array
*/
private static function backupVariables(array $variables, $templateVariableContainer): array
{
$backups = [];
foreach ($variables as $variableName => $variableValue) {
if ($templateVariableContainer->exists($variableName)) {
$backups[$variableName] = $templateVariableContainer->get($variableName);
$templateVariableContainer->remove($variableName);
}
$templateVariableContainer->add($variableName, $variableValue);
}
return $backups;
}
/**
* @param array $variables
* @param array $backups
* @param VariableProviderInterface $templateVariableContainer
*/
private static function restoreVariables(array $variables, array $backups, $templateVariableContainer): void
{
foreach ($variables as $variableName => $variableValue) {
$templateVariableContainer->remove($variableName);
if (true === isset($backups[$variableName])) {
$templateVariableContainer->add($variableName, $variableValue);
}
}
}
}
@@ -0,0 +1,329 @@
<?php
namespace GeorgRinger\News\ViewHelpers;
use GeorgRinger\News\Domain\Model\News;
use GeorgRinger\News\Service\SettingsService;
use TYPO3\CMS\Backend\Utility\BackendUtility;
/**
* 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\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
/**
* ViewHelper to render links from news records to detail view or page
*
* # Example: Basic link
* <code>
* <n:link newsItem="{newsItem}" settings="{settings}">
* {newsItem.title}
* </n:link>
* </code>
* <output>
* A link to the given news record using the news title as link text
* </output>
*
* # Example: Set an additional attribute
* # Description: Available: class, dir, id, lang, style, title, accesskey, tabindex, onclick
* <code>
* <n:link newsItem="{newsItem}" settings="{settings}" class="a-link-class">fo</n:link>
* </code>
* <output>
* <a href="link" class="a-link-class">fo</n:link>
* </output>
*
* # Example: Return the link only
* <code>
* <n:link newsItem="{newsItem}" settings="{settings}" uriOnly="1" />
* </code>
* <output>
* The uri is returned
* </output>
*/
class LinkViewHelper extends AbstractTagBasedViewHelper
{
/**
* @var string
*/
protected $tagName = 'a';
/**
* @var \GeorgRinger\News\Service\SettingsService
*/
protected $pluginSettingsService;
/**
* @var array
*/
protected $detailPidDeterminationCallbacks = [
'flexform' => 'getDetailPidFromFlexform',
'categories' => 'getDetailPidFromCategories',
'default' => 'getDetailPidFromDefaultDetailPid',
];
/** @var ContentObjectRenderer */
protected $cObj;
/**
* @param \GeorgRinger\News\Service\SettingsService $pluginSettingsService
*
* @return void
*/
public function injectSettingsService(SettingsService $pluginSettingsService): void
{
$this->pluginSettingsService = $pluginSettingsService;
}
public function initializeArguments()
{
parent::initializeArguments();
$this->registerUniversalTagAttributes();
$this->registerArgument('newsItem', News::class, 'news item', true);
$this->registerArgument('settings', 'array', 'Settings', false, []);
$this->registerArgument('uriOnly', 'bool', 'url only', false, false);
$this->registerArgument('configuration', 'array', 'configuration', false, []);
$this->registerArgument('content', 'string', 'content', false, '');
$this->registerArgument('section', 'string', 'Anchor for links', false);
}
/**
* Render link to news item or internal/external pages
*
* @return null|string link
*/
public function render(): ?string
{
/** @var News $newsItem */
$newsItem = $this->arguments['newsItem'];
$settings = $this->arguments['settings'] ?? [];
$uriOnly = $this->arguments['uriOnly'] ?? false;
$configuration = $this->arguments['configuration'] ?? [];
$content = $this->arguments['content'] ?? '';
$tsSettings = (array)$this->pluginSettingsService->getSettings();
ArrayUtility::mergeRecursiveWithOverrule($tsSettings, (array)$settings);
// Options with stdWrap enabled won't override $tsSettings as intended here: override them explicit.
if (isset($settings['useStdWrap']) && $settings['useStdWrap']) {
foreach (GeneralUtility::trimExplode(',', $settings['useStdWrap'], true) as $stdWrapProperty) {
if (is_array($tsSettings[$stdWrapProperty]) && array_key_exists($stdWrapProperty, $settings)) {
$tsSettings[$stdWrapProperty] = $settings[$stdWrapProperty];
}
}
}
$this->init();
$linkedContent = $this->renderChildren();
if ($newsItem === null) {
return $linkedContent;
}
$newsType = (int)$newsItem->getType();
switch ($newsType) {
// internal news
case 1:
$configuration['parameter'] = $newsItem->getInternalurl();
break;
// external news
case 2:
$configuration['parameter'] = $newsItem->getExternalurl();
break;
// normal news record
default:
$configuration = $this->getLinkToNewsItem($newsItem, $tsSettings, $configuration);
}
$url = $this->cObj->typoLink_URL($configuration);
// link could not be generated
if ($url === '' || $linkedContent === $url) {
return $linkedContent;
}
if ($this->hasArgument('section')) {
$url .= '#' . $this->arguments['section'];
}
if ($uriOnly) {
return $url;
}
if (isset($tsSettings['link']['typesOpeningInNewWindow'])) {
if (GeneralUtility::inList($tsSettings['link']['typesOpeningInNewWindow'], $newsType)) {
$this->tag->addAttribute('target', '_blank');
}
}
if (!$this->tag->hasAttribute('target')) {
$target = $this->getTargetConfiguration($configuration);
if (!empty($target)) {
$this->tag->addAttribute('target', $target);
}
}
$this->tag->addAttribute('href', $url);
if (empty($content)) {
$content = $linkedContent;
}
$this->tag->setContent($content);
return $this->tag->render();
}
/**
* Generate the link configuration for the link to the news item
*
* @param News $newsItem
* @param array $tsSettings
* @param array $configuration
* @return array
*/
protected function getLinkToNewsItem(
News $newsItem,
$tsSettings,
array $configuration = []
): array {
if (!isset($configuration['parameter'])) {
$detailPid = 0;
// if TS is not set, prefer flexform setting
if (!isset($tsSettings['detailPidDetermination'])) {
$detailPidDeterminationMethods = ['flexform'];
} else {
$detailPidDeterminationMethods = GeneralUtility::trimExplode(
',',
$tsSettings['detailPidDetermination'],
true
);
}
foreach ($detailPidDeterminationMethods as $determinationMethod) {
if ($callback = $this->detailPidDeterminationCallbacks[$determinationMethod]) {
if ($detailPid = call_user_func([$this, $callback], $tsSettings, $newsItem)) {
break;
}
}
}
if (!$detailPid && isset($GLOBALS['TSFE'])) {
$detailPid = $GLOBALS['TSFE']->id;
}
$configuration['parameter'] = $detailPid;
}
$configuration['additionalParams'] = (isset($configuration['additionalParams']) ? $configuration['additionalParams'] : '') . '&tx_news_pi1[news]=' . $this->getNewsId($newsItem);
$configuration['additionalParams'] .= '&tx_news_pi1[controller]=News&tx_news_pi1[action]=detail';
// Add date as human readable
if (isset($tsSettings['link']['hrDate']) && $tsSettings['link']['hrDate'] == 1 || isset($tsSettings['link']['hrDate']['_typoScriptNodeValue']) && $tsSettings['link']['hrDate']['_typoScriptNodeValue'] == 1) {
$dateTime = $newsItem->getDatetime();
if (!is_null($dateTime)) {
if (!empty($tsSettings['link']['hrDate']['day'])) {
$configuration['additionalParams'] .= '&tx_news_pi1[day]=' . $dateTime->format($tsSettings['link']['hrDate']['day']);
}
if (!empty($tsSettings['link']['hrDate']['month'])) {
$configuration['additionalParams'] .= '&tx_news_pi1[month]=' . $dateTime->format($tsSettings['link']['hrDate']['month']);
}
if (!empty($tsSettings['link']['hrDate']['year'])) {
$configuration['additionalParams'] .= '&tx_news_pi1[year]=' . $dateTime->format($tsSettings['link']['hrDate']['year']);
}
}
}
return $configuration;
}
/**
* @param News $newsItem
* @return int
*/
protected function getNewsId(News $newsItem): int
{
$uid = $newsItem->getUid();
// If a user is logged in and not in live workspace
if (isset($GLOBALS['BE_USER']) && $GLOBALS['BE_USER']->workspace > 0) {
$record = BackendUtility::getLiveVersionOfRecord(
'tx_news_domain_model_news',
$newsItem->getUid()
);
if ($record['uid']) {
$uid = $record['uid'];
}
}
return $uid;
}
/**
* @param array $configuration
* @return string
*/
protected function getTargetConfiguration(array $configuration): string
{
$configuration['returnLast'] = 'target';
return $this->cObj->typoLink('dummy', $configuration);
}
/**
* Gets detailPid from categories of the given news item. First will be return.
*
* @param array $settings
* @param News $newsItem
* @return int
*/
protected function getDetailPidFromCategories($settings, $newsItem): int
{
$detailPid = 0;
if ($newsItem->getCategories()) {
foreach ($newsItem->getCategories() as $category) {
if ($detailPid = (int)$category->getSinglePid()) {
break;
}
}
}
return $detailPid;
}
/**
* Gets detailPid from defaultDetailPid setting
*
* @param array $settings
* @param News $newsItem
* @return int
*/
protected function getDetailPidFromDefaultDetailPid($settings, $newsItem): int
{
return isset($settings['defaultDetailPid']) ? (int)$settings['defaultDetailPid'] : 0;
}
/**
* Gets detailPid from flexform of current plugin.
*
* @param array $settings
* @param News $newsItem
* @return int
*/
protected function getDetailPidFromFlexform($settings, $newsItem): int
{
return isset($settings['detailPid']) ? (int)$settings['detailPid'] : 0;
}
/**
* Initialize properties
*
* @return void
*/
protected function init(): void
{
$this->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
}
}
@@ -0,0 +1,93 @@
<?php
namespace GeorgRinger\News\ViewHelpers;
/**
* 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\Page\PageRenderer;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
/**
* ViewHelper to render meta tags
*
* # Example: Basic Example: News title as og:title meta tag
* <code>
* <n:metaTag property="og:title" content="{newsItem.title}" />
* </code>
* <output>
* <meta property="og:title" content="TYPO3 is awesome" />
* </output>
*
* # Example: Force the attribute "name"
* <code>
* <n:metaTag name="keywords" content="{newsItem.keywords}" />
* </code>
* <output>
* <meta name="keywords" content="news 1, news 2" />
* </output>
*/
class MetaTagViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;
/**
* Arguments initialization
*/
public function initializeArguments()
{
$this->registerArgument('property', 'string', 'Property of meta tag', false, '', false);
$this->registerArgument('name', 'string', 'Content of meta tag using the name attribute', false, '', false);
$this->registerArgument('content', 'string', 'Content of meta tag', true, null, false);
$this->registerArgument('useCurrentDomain', 'boolean', 'Use current domain', false, false);
$this->registerArgument('forceAbsoluteUrl', 'boolean', 'Force absolut domain', false, false);
}
public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
{
// Skip if current record is part of tt_content CType shortcut
if (!empty($GLOBALS['TSFE']->recordRegister)
&& is_array($GLOBALS['TSFE']->recordRegister)
&& strpos(array_keys($GLOBALS['TSFE']->recordRegister)[0], 'tt_content:') !== false
&& !empty($GLOBALS['TSFE']->currentRecord)
&& strpos($GLOBALS['TSFE']->currentRecord, 'tx_news_domain_model_news:') !== false
) {
return;
}
$useCurrentDomain = $arguments['useCurrentDomain'];
$forceAbsoluteUrl = $arguments['forceAbsoluteUrl'];
$content = (string)$arguments['content'];
// set current domain
if ($useCurrentDomain) {
$content = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL');
}
// prepend current domain
if ($forceAbsoluteUrl) {
$parsedPath = parse_url($content);
if (is_array($parsedPath) && !isset($parsedPath['host'])) {
$content =
rtrim(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'), '/')
. '/'
. ltrim($content, '/');
}
}
if ($content !== '') {
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
if ($arguments['property']) {
$pageRenderer->setMetaTag('property', $arguments['property'], $content);
} elseif ($arguments['name']) {
$pageRenderer->setMetaTag('name', $arguments['name'], $content);
}
}
}
}
@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace GeorgRinger\News\ViewHelpers\MultiCategoryLink;
/**
* 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\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInterface;
/**
* ViewHelper to get additional params including add/remove categories from list
*
* # Example: Basic Example
* # Description: Render the content of the VH as page title
* <code>
* <f:link.page title="{category.item.title}" pageUid="{settings.listPid}"
* additionalParams="{n:multiCategoryLink.arguments(
* mode:'add',
* item:category.item.uid,
* list:overwriteDemand.categories)}">link
* </f:link.page>
* </code>
* <output>
* <title>TYPO3 is awesome</title>
* </output>
*/
class ArgumentsViewHelper extends AbstractViewHelper implements ViewHelperInterface
{
use CompileWithRenderStatic;
/**
* Initialize arguments
*/
public function initializeArguments()
{
$this->registerArgument('mode', 'string', 'Mode, either "add" or "remove"', true);
$this->registerArgument('list', 'string', 'Category list', false, '');
$this->registerArgument('item', 'int', 'Category id', true);
$this->registerArgument('additionalParams', 'array', 'Additional params', false, []);
parent::initializeArguments();
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
*
* @return array
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
): array {
if ($arguments['mode'] !== 'add' && $arguments['mode'] !== 'remove') {
throw new Exception('Mode must be either "add" or "remove', 1522293549);
}
$allArguments = (array)$arguments['additionalParams'];
$categoryId = $arguments['item'];
$categoryList = $arguments['list'];
if ($arguments['mode'] === 'add') {
if (!GeneralUtility::inList($categoryList, $categoryId)) {
$categoryList .= ',' . $categoryId;
}
} else {
$categoryList = GeneralUtility::rmFromList($categoryId, $categoryList);
}
if (!empty($categoryList)) {
$categoryList = trim($categoryList, ',');
$categoryArray = [
'tx_news_pi1' => [
'overwriteDemand' => [
'categories' => $categoryList
]
]
];
ArrayUtility::mergeRecursiveWithOverrule($allArguments, $categoryArray);
}
return $allArguments;
}
}
@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace GeorgRinger\News\ViewHelpers\MultiCategoryLink;
/**
* 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\AbstractConditionViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInterface;
/**
* ViewHelper to check if th
*
* # Example: Basic example
* <code>
* <n:multiCategoryLink.isCategoryActive list="{overwriteDemand.categories}" item="{category.uid}">
* do stuff
* </n:multiCategoryLink.isCategoryActive>
* </code>
* <output>
* "du stuff" will be shown if the category is currently in demand
* </output>
*/
class IsCategoryActiveViewHelper extends AbstractConditionViewHelper implements ViewHelperInterface
{
/**
* Initialize arguments
*/
public function initializeArguments()
{
$this->registerArgument('list', 'string', 'List of news', false, '');
$this->registerArgument('item', 'int', 'Category id', true);
parent::initializeArguments();
}
/**
* @param array|null $arguments
* @return bool
*/
protected static function evaluateCondition($arguments = null): bool
{
if (empty($arguments['list'])) {
return false;
}
return GeneralUtility::inList($arguments['list'], $arguments['item']);
}
/**
* @return mixed
*/
public function render()
{
if (static::evaluateCondition($this->arguments)) {
return $this->renderThenChild();
}
return $this->renderElseChild();
}
}
@@ -0,0 +1,229 @@
<?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 . '>';
}
}
@@ -0,0 +1,34 @@
<?php
namespace GeorgRinger\News\ViewHelpers;
/**
* 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 TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
class RemoveMediaTagsViewHelper extends AbstractViewHelper
{
protected $escapingInterceptorEnabled = false;
protected $escapeOutput = false;
protected $escapeChildren = false;
/**
* @var string[]
*/
protected $tags = ['[media]'];
/**
* @return mixed
*/
public function render()
{
$content = $this->renderChildren();
return str_replace($this->tags, '', $content);
}
}
@@ -0,0 +1,195 @@
<?php
namespace GeorgRinger\News\ViewHelpers;
/**
* 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 GeorgRinger\News\Domain\Model\News;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\Rendering\RendererRegistry;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Service\ImageService;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
class RenderMediaViewHelper extends AbstractViewHelper
{
protected $escapeOutput = false;
protected $escapingInterceptorEnabled = false;
/**
* @var string
*/
protected $mediaTag = '/\\[media\\]/';
protected $replaceMediaTag = '/(?:<p>\s*)?\\[media\\](?:\s*<\/p>)?/';
/**
* @var string
*/
protected $imgClass = '';
/**
* @var string
*/
protected $videoClass = '';
/**
* @var string
*/
protected $audioClass = '';
/**
* Initialize all arguments. You need to override this method and call
* $this->registerArgument(...) inside this method, to register all your arguments.
*
* @api
*/
public function initializeArguments()
{
$this->registerArgument('news', 'object', 'the news post', true);
$this->registerArgument('imgClass', 'string', 'add css class to images');
$this->registerArgument('videoClass', 'string', 'wrap videos in a div with this class');
$this->registerArgument('audioClass', 'string', 'wrap audio files in a div with this class');
$this->registerArgument('fileIndex', 'int', 'index of image to start with', false, 0);
}
/**
* @param \TYPO3\CMS\Core\Resource\FileInterface $image
* @return string
*/
private function renderImage(\TYPO3\CMS\Core\Resource\FileInterface $image): string
{
if ($this->objectManager === null) {
$this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
}
$imageService = $this->objectManager->get(ImageService::class);
$cropString = '';
if ($image->hasProperty('crop')) {
$cropString = $image->getProperty('crop');
}
$cropVariantCollection = CropVariantCollection::create((string)$cropString);
$cropVariant = $this->arguments['cropVariant'] ?: 'default';
$cropArea = $cropVariantCollection->getCropArea($cropVariant);
$processingInstructions = [
'width' => null,
'height' => null,
'crop' => $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($image)
];
$processedImage = $imageService->applyProcessingInstructions($image, $processingInstructions);
$imageUri = $imageService->getImageUri($processedImage);
$alt = trim($image->getProperty('alternative'));
$title = trim($image->getProperty('title'));
$description = trim($image->getProperty('description'));
$imageAttributes = [
'src' => $imageUri,
'alt' => $alt ?: ($title ?: ''),
'title' => $title
];
if (!empty($this->imgClass)) {
$imageAttributes['class'] = $this->imgClass;
}
$imageAttributes = array_reduce(
array_keys($imageAttributes),
function ($carry, $key) use ($imageAttributes) {
return $carry . ' ' . $key . '="' . htmlspecialchars($imageAttributes[$key]) . '"';
},
''
);
if (!empty($description)) {
$description = '<figcaption>' . htmlspecialchars($description) . '</figcaption>';
}
return '<figure>' . '<img ' . $imageAttributes . ' />' . $description . '</figure>';
}
/**
* Replace the [media] tags with the output of the according media render output
*
* @param string $content
* @param array $files
* @return string
*/
private function renderMedia($content, array $files): string
{
$fileIndex = $this->arguments['fileIndex'];
preg_match_all($this->mediaTag, $content, $matches);
foreach ($matches[0] as $_) {
/** @var \GeorgRinger\News\Domain\Model\FileReference $file */
$file = null;
/** @var \TYPO3\CMS\Core\Resource\FileReference $media */
$media = null;
// check if a file is present for current media tag
if (count($files) <= $fileIndex) {
break;
}
$file = $files[$fileIndex++];
if ($file === null) {
break;
}
$media = $file->getOriginalResource();
$fileRenderer = RendererRegistry::getInstance()->getRenderer($media);
// if a renderer is configured for the file type use this renderer
if ($fileRenderer !== null) {
$media_tag = $fileRenderer->render($media, 0, 0);
// check if media tag needs to be wrapped in div, depends on type of media file
$wrapClass= '';
if ($media->getType() === File::FILETYPE_VIDEO) {
$wrapClass = $this->videoClass;
} elseif ($media->getType() === File::FILETYPE_AUDIO) {
$wrapClass = $this->audioClass;
}
if (!empty($wrapClass)) {
$media_tag = '<div class="' . $wrapClass . '">' . $media_tag . '</div>';
}
}
// fallback to image rendering
else {
$media_tag = $this->renderImage($media);
}
// replace one tag in content with render output
$content = preg_replace($this->replaceMediaTag, $media_tag, $content, 1);
}
return $content;
}
/**
* @return string
*/
public function render(): string
{
/** @var News $news */
$news = $this->arguments['news'];
$this->imgClass = htmlspecialchars($this->arguments['imgClass']);
$this->videoClass = htmlspecialchars($this->arguments['videoClass']);
$this->audioClass = htmlspecialchars($this->arguments['audioClass']);
$mediaFiles = (array)$news->getMediaNonPreviews();
$content = $this->renderChildren();
$content = $this->renderMedia($content, $mediaFiles);
return $content;
}
}
@@ -0,0 +1,250 @@
<?php
namespace GeorgRinger\News\ViewHelpers;
use GeorgRinger\News\Domain\Model\News;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException;
use TYPO3\CMS\Core\Context\LanguageAspect;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
/**
* 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.
*/
/**
* ViewHelper for a **simple** prev/next link.
* Only the pid is taken into account, nothing else!
*
* # Example: Basic example
* <code>
*
* <n:simplePrevNext pidList="{newsItem.pid}" news="{newsItem}" as="paginated" sortField="datetime">
* <f:if condition="{paginated}">
* <ul class="prev-next">
* <f:if condition="{paginated.prev}">
* <li class="previous">
* <n:link newsItem="{paginated.prev}" settings="{settings}">
* {paginated.prev.title}
* </n:link>
* </li>
* </f:if>
* <f:if condition="{paginated.next}">
* <li class="next">
* <n:link newsItem="{paginated.next}" settings="{settings}" class="next">
* {paginated.next.title}
* </n:link>
* </li>
* </f:if>
* </ul>
* </f:if>
* </n:simplePrevNext>
*
* The attributes includeExternalType & includeInternalType allow to include internal and
* external news types.
*
* </code>
* <output>
* Menu with 2 li items with the link to the previous and next news item.
* </output>
*/
class SimplePrevNextViewHelper extends AbstractViewHelper
{
/* @var $dataMapper \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper */
protected $dataMapper;
/**
* @var bool
*/
protected $escapeOutput = false;
/**
* Inject the DataMapper
*
* @param \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper
*
* @return void
*/
public function injectDataMapper(DataMapper $dataMapper): void
{
$this->dataMapper = $dataMapper;
}
/**
* Initialize arguments.
*/
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('news', News::class, 'news item', true);
$this->registerArgument('pidList', 'string', 'pid list', false, '');
$this->registerArgument('sortField', 'string', 'sort field', false, 'datetime');
$this->registerArgument('as', 'string', 'as', true);
$this->registerArgument('includeInternalType', 'boolean', 'Include internal news types', false, false);
$this->registerArgument('includeExternalType', 'bool', 'Include external news types', false, false);
}
/**
* @return string
*/
public function render(): string
{
$neighbours = $this->getNeighbours($this->arguments['news'], $this->arguments['pidList'], $this->arguments['sortField']);
$as = $this->arguments['as'];
$mapped = $this->mapResultToObjects($neighbours);
$this->templateVariableContainer->add($as, $mapped);
$output = $this->renderChildren();
$this->templateVariableContainer->remove($as);
return $output;
}
/**
* Map the array from DB to an understandable output
*
* @param array $result
* @return array
*/
protected function mapResultToObjects(array $result): array
{
$out = [];
foreach ($result as $_id => $single) {
$out[$_id] = $this->getObject($single['uid']);
}
return $out;
}
/**
* Get the news object from the given id
*
* @param int $id
* @return mixed|null
* @throws AspectNotFoundException
*/
protected function getObject($id)
{
$record = null;
$rawRecord = $this->getRawRecord($id);
/** @var LanguageAspect $languageAspect */
$languageAspect = GeneralUtility::makeInstance(Context::class)->getAspect('language');
if (isset($GLOBALS['TSFE']) && is_object($GLOBALS['TSFE']) && $languageAspect->getContentId() > 0) {
$overlay = $GLOBALS['TSFE']->sys_page->getRecordOverlay(
'tx_news_domain_model_news',
$rawRecord,
$languageAspect->getContentId(),
$languageAspect->getLegacyOverlayType()
);
if (!is_null($overlay)) {
$rawRecord = $overlay;
}
}
if (is_array($rawRecord)) {
$records = $this->dataMapper->map(News::class, [$rawRecord]);
$record = array_shift($records);
}
return $record;
}
/**
* @param News $news
* @param string $pidList
* @param string $sortField
* @return array
*/
protected function getNeighbours(News $news, string $pidList, string $sortField): array
{
$data = [];
$pidList = empty($pidList) ? (string)$news->getPid() : $pidList;
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('tx_news_domain_model_news');
foreach (['prev', 'next'] as $label) {
$queryBuilder = $connection->createQueryBuilder();
$extraWhere = [
$queryBuilder->expr()->neq('uid', $queryBuilder->createNamedParameter($news->getUid(), \PDO::PARAM_INT))
];
if ((bool)($this->arguments['includeInternalType'] ?? false) === false) {
$extraWhere[] = $queryBuilder->expr()->neq('type', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT));
}
if ((bool)($this->arguments['includeExternalType'] ?? false) === false) {
$extraWhere[] = $queryBuilder->expr()->neq('type', $queryBuilder->createNamedParameter(2, \PDO::PARAM_INT));
}
$getter = 'get' . ucfirst($sortField) . '';
if ($news->$getter() instanceof \DateTime) {
if ($label === 'prev') {
$extraWhere[] = $queryBuilder->expr()->lt($sortField, $queryBuilder->createNamedParameter($news->$getter()->getTimestamp(), \PDO::PARAM_INT));
} else {
$extraWhere[] = $queryBuilder->expr()->gt($sortField, $queryBuilder->createNamedParameter($news->$getter()->getTimestamp(), \PDO::PARAM_INT));
}
} else {
if ($label === 'prev') {
$extraWhere[] = $queryBuilder->expr()->lt($sortField, $queryBuilder->createNamedParameter($news->$getter(), \PDO::PARAM_STR));
} else {
$extraWhere[] = $queryBuilder->expr()->gt($sortField, $queryBuilder->createNamedParameter($news->$getter(), \PDO::PARAM_STR));
}
}
$row = $queryBuilder
->select('*')
->from('tx_news_domain_model_news')
->where(
$queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
$queryBuilder->expr()->in('pid', $queryBuilder->createNamedParameter(explode(',', $pidList), Connection::PARAM_INT_ARRAY))
)
->andWhere(...$extraWhere)
->setMaxResults(1)
->orderBy($sortField, ($label === 'prev' ? 'desc' : 'asc'))
->execute()->fetch();
if (is_array($row)) {
$data[$label] = $row;
}
}
return $data;
}
/**
* @return QueryBuilder
*/
protected function getQueryBuilder(): QueryBuilder
{
return GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_news_domain_model_news');
}
/**
* @param int $id
* @return array
*/
protected function getRawRecord(int $id): ?array
{
$queryBuilder = $this->getQueryBuilder();
$rawRecord = $queryBuilder
->select('*')
->from('tx_news_domain_model_news')
->where(
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT))
)
->setMaxResults(1)
->execute()->fetch();
return $rawRecord;
}
}
@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace GeorgRinger\News\ViewHelpers\Tag;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInterface;
/**
* 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.
*/
/**
* Get usage count
*
* Example usage
* {n:tag.count(tagUid:tag.uid) -> f:variable(name: 'tagUsageCount')}
* {tagUsageCount}
*/
class CountViewHelper extends AbstractViewHelper implements ViewHelperInterface
{
use CompileWithRenderStatic;
/**
* Initialize arguments
*/
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('tagUid', 'int', 'Uid of the tag', true);
}
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
* @return int
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
): int {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_news_domain_model_news');
$count = $queryBuilder
->count('tx_news_domain_model_news.title')
->from('tx_news_domain_model_news')
->rightJoin(
'tx_news_domain_model_news',
'tx_news_domain_model_news_tag_mm',
'tx_news_domain_model_news_tag_mm',
$queryBuilder->expr()->eq('tx_news_domain_model_news.uid', $queryBuilder->quoteIdentifier('tx_news_domain_model_news_tag_mm.uid_local'))
)
->rightJoin(
'tx_news_domain_model_news_tag_mm',
'tx_news_domain_model_tag',
'tx_news_domain_model_tag',
$queryBuilder->expr()->eq('tx_news_domain_model_tag.uid', $queryBuilder->quoteIdentifier('tx_news_domain_model_news_tag_mm.uid_foreign'))
)
->where(
$queryBuilder->expr()->eq('tx_news_domain_model_tag.uid', $queryBuilder->createNamedParameter($arguments['tagUid'], \PDO::PARAM_INT))
)
->execute()
->fetchColumn(0);
return $count;
}
}
@@ -0,0 +1,63 @@
<?php
namespace GeorgRinger\News\ViewHelpers;
/**
* 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 TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInterface;
/**
* ViewHelper to get the target out of the typolink
*
* # Example: Basic Example
* # Description: {relatedLink.uri} is defined as "123 _blank"
* <code>
* <f:link.typolink parameter="{relatedLink.uri}" target="{n:targetLink(link:relatedLink.uri)}">Link</f:link.typolink>
* </code>
* <output>
* A link to the page with uid 123 and target set to "_blank"
* </output>
*/
class TargetLinkViewHelper extends AbstractViewHelper implements ViewHelperInterface
{
use CompileWithRenderStatic;
/**
* Initialize arguments.
*/
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('link', 'string', 'Link', true);
}
/**
* Returns the correct target of a typolink
*
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
* @return mixed
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
$params = explode(' ', $arguments['link']);
// The target is on the 2nd place and must start with a '_'
if (count($params) >= 2 && substr($params[1], 0, 1) === '_') {
return $params[1];
}
return '';
}
}
@@ -0,0 +1,62 @@
<?php
namespace GeorgRinger\News\ViewHelpers;
/**
* 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 GeorgRinger\News\Seo\NewsTitleProvider;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInterface;
/**
* ViewHelper to render the page title
*
* # Example: Basic Example
* # Description: Render the content of the VH as page title
* <code>
* <n:titleTag>{newsItem.title}</n:titleTag>
* </code>
* <output>
* <title>TYPO3 is awesome</title>
* </output>
*/
class TitleTagViewHelper extends AbstractViewHelper implements ViewHelperInterface
{
use CompileWithRenderStatic;
/**
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
*
* @return void
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
): void {
// Skip if current record is part of tt_content CType shortcut
if (!empty($GLOBALS['TSFE']->recordRegister)
&& is_array($GLOBALS['TSFE']->recordRegister)
&& strpos(array_keys($GLOBALS['TSFE']->recordRegister)[0], 'tt_content:') !== false
&& !empty($GLOBALS['TSFE']->currentRecord)
&& strpos($GLOBALS['TSFE']->currentRecord, 'tx_news_domain_model_news:') !== false
) {
return;
}
$content = trim($renderChildrenClosure());
if (!empty($content)) {
GeneralUtility::makeInstance(NewsTitleProvider::class)->setTitle($content);
}
}
}