Add BG offset
This commit is contained in:
@@ -81,6 +81,8 @@ final class ContainerChildrenProcessor implements DataProcessorInterface
|
|||||||
// so without this they would show up in every single child's `data`.
|
// so without this they would show up in every single child's `data`.
|
||||||
'tx_vitec_bg_variant', 'tx_vitec_bg_image', 'tx_vitec_bg_size',
|
'tx_vitec_bg_variant', 'tx_vitec_bg_image', 'tx_vitec_bg_size',
|
||||||
'tx_vitec_bg_size_percent', 'tx_vitec_bg_position',
|
'tx_vitec_bg_size_percent', 'tx_vitec_bg_position',
|
||||||
|
'tx_vitec_bg_pos_top', 'tx_vitec_bg_pos_bottom',
|
||||||
|
'tx_vitec_bg_pos_left', 'tx_vitec_bg_pos_right',
|
||||||
'tx_vitec_col1_align', 'tx_vitec_col1_justify',
|
'tx_vitec_col1_align', 'tx_vitec_col1_justify',
|
||||||
'tx_vitec_col2_align', 'tx_vitec_col2_justify',
|
'tx_vitec_col2_align', 'tx_vitec_col2_justify',
|
||||||
'tx_vitec_col3_align', 'tx_vitec_col3_justify',
|
'tx_vitec_col3_align', 'tx_vitec_col3_justify',
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class ContainerBackgroundRenderer
|
|||||||
return (string)json_encode([
|
return (string)json_encode([
|
||||||
'image' => $image,
|
'image' => $image,
|
||||||
'size' => $this->resolveSize($row),
|
'size' => $this->resolveSize($row),
|
||||||
'position' => (string)($row['tx_vitec_bg_position'] ?? 'center center'),
|
'position' => $this->resolvePosition($row),
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return '';
|
return '';
|
||||||
@@ -101,4 +101,54 @@ class ContainerBackgroundRenderer
|
|||||||
$percent = (int)($row['tx_vitec_bg_size_percent'] ?? 0);
|
$percent = (int)($row['tx_vitec_bg_size_percent'] ?? 0);
|
||||||
return $percent > 0 ? $percent . '%' : 'auto';
|
return $percent > 0 ? $percent . '%' : 'auto';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stored position, again resolved into one CSS-ready value.
|
||||||
|
*
|
||||||
|
* The four custom fields are edge offsets, but CSS cannot take a top AND a
|
||||||
|
* bottom offset at once, so they are folded into the percentage form. That
|
||||||
|
* form says exactly the same thing: in `background-position` a percentage
|
||||||
|
* aligns the same point of image and container, so "10% from the right edge"
|
||||||
|
* IS `90%`. Nothing is lost in the translation.
|
||||||
|
*
|
||||||
|
* Per axis the near edge wins when both are filled - `left` over `right`,
|
||||||
|
* `top` over `bottom` - because two opposing offsets contradict each other
|
||||||
|
* and silently picking one beats emitting something invalid.
|
||||||
|
*
|
||||||
|
* @param array<string,mixed> $row
|
||||||
|
*/
|
||||||
|
private function resolvePosition(array $row): string
|
||||||
|
{
|
||||||
|
$position = (string)($row['tx_vitec_bg_position'] ?? 'center center');
|
||||||
|
if ($position !== 'custom') {
|
||||||
|
return $position;
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = $this->axisPercent($row, 'tx_vitec_bg_pos_left', 'tx_vitec_bg_pos_right');
|
||||||
|
$y = $this->axisPercent($row, 'tx_vitec_bg_pos_top', 'tx_vitec_bg_pos_bottom');
|
||||||
|
|
||||||
|
return $x . '% ' . $y . '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One axis as a percentage. An empty field means "centred on this axis" -
|
||||||
|
* which is why the columns are nullable: 0 is a legitimate offset (flush
|
||||||
|
* against that edge) and has to stay distinguishable from "not set".
|
||||||
|
*
|
||||||
|
* @param array<string,mixed> $row
|
||||||
|
*/
|
||||||
|
private function axisPercent(array $row, string $nearField, string $farField): int
|
||||||
|
{
|
||||||
|
$near = $row[$nearField] ?? null;
|
||||||
|
if ($near !== null && $near !== '') {
|
||||||
|
return (int)$near;
|
||||||
|
}
|
||||||
|
|
||||||
|
$far = $row[$farField] ?? null;
|
||||||
|
if ($far !== null && $far !== '') {
|
||||||
|
return 100 - (int)$far;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
|
|||||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||||
use TYPO3\CMS\Core\Service\FlexFormService;
|
use TYPO3\CMS\Core\Service\FlexFormService;
|
||||||
|
use Evomedien\Vitec\Service\LinkResolver;
|
||||||
use Evomedien\Vitec\Service\RteResolver;
|
use Evomedien\Vitec\Service\RteResolver;
|
||||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||||
use TYPO3\CMS\Extbase\Service\ImageService;
|
use TYPO3\CMS\Extbase\Service\ImageService;
|
||||||
@@ -326,13 +327,14 @@ final class NewsJsonRenderer
|
|||||||
{
|
{
|
||||||
$uid = (int)($news['uid'] ?? 0);
|
$uid = (int)($news['uid'] ?? 0);
|
||||||
$pathSegment = (string)($news['path_segment'] ?? '');
|
$pathSegment = (string)($news['path_segment'] ?? '');
|
||||||
$urls = $this->buildNewsUrls($pathSegment, $settings);
|
$urls = $this->buildNewsUrls($news, $settings);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'uid' => $uid,
|
'uid' => $uid,
|
||||||
'title' => (string)($news['title'] ?? ''),
|
'title' => (string)($news['title'] ?? ''),
|
||||||
'alternativeTitle' => (string)($news['alternative_title'] ?? ''),
|
'alternativeTitle' => (string)($news['alternative_title'] ?? ''),
|
||||||
'pathSegment' => $pathSegment,
|
'pathSegment' => $pathSegment,
|
||||||
|
'type' => (string)($news['type'] ?? '0'),
|
||||||
'detailUrl' => $urls['detailUrl'],
|
'detailUrl' => $urls['detailUrl'],
|
||||||
'canonicalUrl' => $urls['canonicalUrl'],
|
'canonicalUrl' => $urls['canonicalUrl'],
|
||||||
'teaser' => (string)($news['teaser'] ?? ''),
|
'teaser' => (string)($news['teaser'] ?? ''),
|
||||||
@@ -352,12 +354,52 @@ final class NewsJsonRenderer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Where a news item actually points.
|
||||||
|
*
|
||||||
|
* EXT:news knows three record types, and only the first one lives on the
|
||||||
|
* news detail route:
|
||||||
|
*
|
||||||
|
* 0 article -> detail page + path_segment (the default below)
|
||||||
|
* 1 internal page -> the page picked in the backend; the record itself
|
||||||
|
* carries no body, so linking it to the detail route
|
||||||
|
* would show a teaser and hide the actual content
|
||||||
|
* 2 external url -> the given address, verbatim
|
||||||
|
*
|
||||||
|
* `detailUrl` is rewritten rather than accompanied by a second field, so a
|
||||||
|
* consumer can keep following one key for every item and never has to branch
|
||||||
|
* on the type. `type` is emitted alongside anyway — a front end may well want
|
||||||
|
* to open an external target in a new tab.
|
||||||
|
*
|
||||||
|
* Falls back to the article URL whenever a type 1/2 record does not yield a
|
||||||
|
* usable target: a link to the teaser beats a dead one.
|
||||||
|
*
|
||||||
|
* @param array<string,mixed> $news
|
||||||
* @param array<string,mixed> $settings
|
* @param array<string,mixed> $settings
|
||||||
* @return array{detailUrl:string, canonicalUrl:string}
|
* @return array{detailUrl:string, canonicalUrl:string}
|
||||||
*/
|
*/
|
||||||
private function buildNewsUrls(string $pathSegment, array $settings): array
|
private function buildNewsUrls(array $news, array $settings): array
|
||||||
{
|
{
|
||||||
$pathSegment = trim($pathSegment, '/');
|
$type = (string)($news['type'] ?? '0');
|
||||||
|
|
||||||
|
if ($type === '2') {
|
||||||
|
$external = trim((string)($news['externalurl'] ?? ''));
|
||||||
|
if ($external !== '') {
|
||||||
|
return ['detailUrl' => $external, 'canonicalUrl' => $external];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type === '1') {
|
||||||
|
$pageUid = $this->extractPageUid((string)($news['internalurl'] ?? ''));
|
||||||
|
$pageUrl = $pageUid > 0 ? LinkResolver::pageUrl($pageUid) : null;
|
||||||
|
if ($pageUrl !== null && $pageUrl !== '') {
|
||||||
|
return [
|
||||||
|
'detailUrl' => $pageUrl,
|
||||||
|
'canonicalUrl' => $this->absoluteUrl($pageUrl),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pathSegment = trim((string)($news['path_segment'] ?? ''), '/');
|
||||||
if ($pathSegment === '') {
|
if ($pathSegment === '') {
|
||||||
return ['detailUrl' => '', 'canonicalUrl' => ''];
|
return ['detailUrl' => '', 'canonicalUrl' => ''];
|
||||||
}
|
}
|
||||||
@@ -380,6 +422,19 @@ final class NewsJsonRenderer
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `internalurl` holds either `t3://page?uid=N` or a bare uid, depending on
|
||||||
|
* how old the record is - the legacy import produced both forms.
|
||||||
|
*/
|
||||||
|
private function extractPageUid(string $internalUrl): int
|
||||||
|
{
|
||||||
|
$internalUrl = trim($internalUrl);
|
||||||
|
if (preg_match('#t3://page\?uid=(\d+)#i', $internalUrl, $m)) {
|
||||||
|
return (int)$m[1];
|
||||||
|
}
|
||||||
|
return ctype_digit($internalUrl) ? (int)$internalUrl : 0;
|
||||||
|
}
|
||||||
|
|
||||||
private function resolvePageSlug(int $pageId): string
|
private function resolvePageSlug(int $pageId): string
|
||||||
{
|
{
|
||||||
if ($pageId <= 0) {
|
if ($pageId <= 0) {
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
|||||||
],
|
],
|
||||||
'tx_vitec_bg_position' => [
|
'tx_vitec_bg_position' => [
|
||||||
'label' => $l . 'bg_position.label',
|
'label' => $l . 'bg_position.label',
|
||||||
|
// Same reason as on bg_size: reveal the custom fields immediately.
|
||||||
|
'onChange' => 'reload',
|
||||||
'config' => [
|
'config' => [
|
||||||
'type' => 'select',
|
'type' => 'select',
|
||||||
'renderType' => 'selectSingle',
|
'renderType' => 'selectSingle',
|
||||||
@@ -104,15 +106,43 @@ use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
|||||||
['label' => $l . 'bg_position.option.bottom_left', 'value' => 'left bottom'],
|
['label' => $l . 'bg_position.option.bottom_left', 'value' => 'left bottom'],
|
||||||
['label' => $l . 'bg_position.option.bottom_center', 'value' => 'center bottom'],
|
['label' => $l . 'bg_position.option.bottom_center', 'value' => 'center bottom'],
|
||||||
['label' => $l . 'bg_position.option.bottom_right', 'value' => 'right bottom'],
|
['label' => $l . 'bg_position.option.bottom_right', 'value' => 'right bottom'],
|
||||||
|
['label' => $l . 'bg_position.option.custom', 'value' => 'custom'],
|
||||||
],
|
],
|
||||||
'default' => 'center center',
|
'default' => 'center center',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Custom position: four edge offsets in percent, shown only when the select
|
||||||
|
// is on "custom". `nullable` matters here - an empty field has to stay
|
||||||
|
// distinguishable from an explicit 0, which is a valid offset (flush against
|
||||||
|
// that edge). Empty means "centred on this axis".
|
||||||
|
//
|
||||||
|
// CSS cannot take a top AND a bottom offset at once, so these four are not
|
||||||
|
// written out literally; ContainerBackgroundRenderer folds them into the
|
||||||
|
// percentage form, where "10% from the right" is simply 90%.
|
||||||
|
$positionOffsets = [];
|
||||||
|
foreach (['top', 'bottom', 'left', 'right'] as $edge) {
|
||||||
|
$positionOffsets['tx_vitec_bg_pos_' . $edge] = [
|
||||||
|
'label' => $l . 'bg_pos.' . $edge,
|
||||||
|
'displayCond' => 'FIELD:tx_vitec_bg_position:=:custom',
|
||||||
|
'config' => [
|
||||||
|
'type' => 'number',
|
||||||
|
'size' => 5,
|
||||||
|
'nullable' => true,
|
||||||
|
'range' => [
|
||||||
|
'lower' => 0,
|
||||||
|
'upper' => 100,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
ExtensionManagementUtility::addTCAcolumns('tt_content', $positionOffsets);
|
||||||
|
|
||||||
// One palette, so size and position sit on a single row under the image.
|
// One palette, so size and position sit on a single row under the image.
|
||||||
$GLOBALS['TCA']['tt_content']['palettes']['vitec_background'] = [
|
$GLOBALS['TCA']['tt_content']['palettes']['vitec_background'] = [
|
||||||
'showitem' => 'tx_vitec_bg_image, --linebreak--, tx_vitec_bg_size, tx_vitec_bg_size_percent, tx_vitec_bg_position',
|
'showitem' => 'tx_vitec_bg_image, --linebreak--, tx_vitec_bg_size, tx_vitec_bg_size_percent, tx_vitec_bg_position,'
|
||||||
|
. ' --linebreak--, tx_vitec_bg_pos_top, tx_vitec_bg_pos_bottom, tx_vitec_bg_pos_left, tx_vitec_bg_pos_right',
|
||||||
];
|
];
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -243,6 +243,21 @@
|
|||||||
<trans-unit id="bg_position.option.bottom_right" resname="bg_position.option.bottom_right">
|
<trans-unit id="bg_position.option.bottom_right" resname="bg_position.option.bottom_right">
|
||||||
<source>Bottom Right</source>
|
<source>Bottom Right</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="bg_position.option.custom" resname="bg_position.option.custom">
|
||||||
|
<source>Custom (use the offsets below)</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="bg_pos.top" resname="bg_pos.top">
|
||||||
|
<source>Top %</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="bg_pos.bottom" resname="bg_pos.bottom">
|
||||||
|
<source>Bottom %</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="bg_pos.left" resname="bg_pos.left">
|
||||||
|
<source>Left %</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="bg_pos.right" resname="bg_pos.right">
|
||||||
|
<source>Right %</source>
|
||||||
|
</trans-unit>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
@@ -211,6 +211,10 @@ CREATE TABLE tt_content (
|
|||||||
tx_vitec_bg_size VARCHAR(20) DEFAULT 'cover' NOT NULL,
|
tx_vitec_bg_size VARCHAR(20) DEFAULT 'cover' NOT NULL,
|
||||||
tx_vitec_bg_size_percent int(11) unsigned DEFAULT '100' NOT NULL,
|
tx_vitec_bg_size_percent int(11) unsigned DEFAULT '100' NOT NULL,
|
||||||
tx_vitec_bg_position VARCHAR(20) DEFAULT 'center center' NOT NULL,
|
tx_vitec_bg_position VARCHAR(20) DEFAULT 'center center' NOT NULL,
|
||||||
|
tx_vitec_bg_pos_top int(11) DEFAULT NULL,
|
||||||
|
tx_vitec_bg_pos_bottom int(11) DEFAULT NULL,
|
||||||
|
tx_vitec_bg_pos_left int(11) DEFAULT NULL,
|
||||||
|
tx_vitec_bg_pos_right int(11) DEFAULT NULL,
|
||||||
tx_vitec_usecase_content int(11) unsigned DEFAULT '0' NOT NULL
|
tx_vitec_usecase_content int(11) unsigned DEFAULT '0' NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user