Datapath stories, grid repairs, menu v2 pure, hero placeholders, fec test
Batch of 2026-10-28/29 plus 2026-11-03: - NEW vitec:import-datapath-stories: 22 story packages from the agency dump (docx + Links/ assets) as usecase records with content, quotes, images, customer logos, download cards; --repair-quotes normalizes attribution lines; asset backfill for existing stories - /products grid incident repaired (fix_products_grid, fix_graphics_grid, check_grid_page): containers unhidden, 46 duplicate cards soft-deleted, section order restored; BuildProductPortfolio and ImportProductCategoryPages now GUARD against re-runs on pages that already carry vitec_cols_% containers - ' double-encoding fixed: render-pipeline importers write literal apostrophes (ENT_NOQUOTES), fix_double_entities literalizes existing rows - ticket quick wins (ticket_quickwins_20261028), broadcast market page cleanup, AttachCardImages market-card handling, SubmarketWorkbook META REPAIR, story content/video fixes (fix_story_*) - ProvideProductCatalog: transitional v1 fields removed (categories uid/link, legacyGroupFields, item products teaser) - pure v2 shape (ticket 0024-FE); search renderer keeps its teaser - BuildProductOverviews: heroes get the numbered placeholder background (placeholders/pages/<uid>-01.png, wide grey fallback) - backfill on existing pages with empty hero_bgimage, attached at creation for new pages; existing references always win (ticket 0026-FE); 40 placeholder PNGs shipped to fileadmin (not tracked) - AttachPageMedia: --depth=3 with sysfolders as pass-through parents for the overview pages' -08 menu tiles - NEW migrations/fec_isolation_test.sh: two-user live test proving fe_comment ticket isolation (backend verified tight, cross-user sighting traced to the FE page-JSON cache, ticket 0027-BE) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9211c8a395
commit
77661bf0b6
22 files changed
+2103
-63
No files matched your search
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Diagnose wie check_grid_447.php, aber mit Seiten-Parameter: Karten,
|
||||
* Grid-Container und Intros einer Seite mit colPos / tx_container_parent /
|
||||
* sorting / hidden / deleted.
|
||||
*
|
||||
* READ-ONLY. Usage: php migrations/check_grid_page.php 569
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
$pid = (int)($argv[1] ?? 0);
|
||||
if ($pid <= 0) {
|
||||
exit("Usage: php migrations/check_grid_page.php <pid>\n");
|
||||
}
|
||||
|
||||
$root = null;
|
||||
$dir = __DIR__;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
if (is_file($dir . '/config/system/settings.php')) { $root = $dir; break; }
|
||||
$dir = dirname($dir);
|
||||
}
|
||||
if ($root === null) { exit("no project root\n"); }
|
||||
$settings = require $root . '/config/system/settings.php';
|
||||
$db = $settings['DB']['Connections']['Default'];
|
||||
$pdo = new PDO(
|
||||
sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $db['host'] ?? 'localhost', $db['port'] ?? 3306, $db['dbname'] ?? ''),
|
||||
$db['user'] ?? '', $db['password'] ?? '',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
|
||||
echo "=== vitec_card / vitec_cols_* / vitec_introparagraph auf pid $pid ===\n";
|
||||
$rows = $pdo->query(
|
||||
"SELECT uid, CType, LEFT(header,32) h, colPos, tx_container_parent par, sorting, hidden, deleted
|
||||
FROM tt_content WHERE pid = $pid AND (CType IN ('vitec_card','vitec_introparagraph') OR CType LIKE 'vitec_cols_%')
|
||||
ORDER BY deleted, colPos, sorting, uid"
|
||||
)->fetchAll();
|
||||
foreach ($rows as $r) {
|
||||
printf(" #%-6d %-22s col=%-4d par=%-6d sort=%-6d hid=%d del=%d %s\n",
|
||||
$r['uid'], $r['CType'], $r['colPos'], $r['par'], $r['sorting'], $r['hidden'], $r['deleted'], $r['h']);
|
||||
}
|
||||
echo "\ncounts: " . count($rows) . " rows\n";
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Ticket 0010-FE Punkt 1: /markets/broadcast-and-telecoms ist eine Mischseite
|
||||
* (alte Pilotseite): vitec_herosection/Container/text/marketlist-Reste plus
|
||||
* colPos220-Legacy NEBEN dem 27.10.-Import. Ziel: exakt das Schema der zehn
|
||||
* anderen Market-Seiten (colPos1: markethero + introparagraph; colPos0:
|
||||
* marketintro, introparagraph, 2 cards, ctabanner).
|
||||
*
|
||||
* Whitelist = die acht Import-Elemente vom 27.10.; ALLES andere auf der
|
||||
* Seite wird soft-geloescht (deleted=1, wiederherstellbar).
|
||||
*
|
||||
* Plain PDO (Muster migrations/check_bg.php).
|
||||
*
|
||||
* Usage: php migrations/cleanup_broadcast_market_page.php (inspect)
|
||||
* php migrations/cleanup_broadcast_market_page.php --apply
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
const PAGE_SLUG = '/markets/broadcast-and-telecoms';
|
||||
// 11342-11349 = Import 27.10. (inkl. der beiden HIDDEN-Stubs customerlogos/
|
||||
// usecaselist, die auch die anderen Market-Seiten tragen); 10293 = das alte
|
||||
// "Explore Broadcast & Telecoms"-Band - der Import hat hier KEINEN eigenen
|
||||
// Explore-Introparagraph angelegt, das Element bleibt die Ueberschrift
|
||||
// ueber den Karten (Inspect-Befund 2026-10-28).
|
||||
const KEEP = [11342, 11343, 11344, 11345, 11346, 11347, 11348, 11349, 10293];
|
||||
|
||||
$apply = in_array('--apply', $argv, true);
|
||||
|
||||
$root = null;
|
||||
$dir = __DIR__;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
if (is_file($dir . '/config/system/settings.php')) {
|
||||
$root = $dir;
|
||||
break;
|
||||
}
|
||||
$dir = dirname($dir);
|
||||
}
|
||||
if ($root === null) {
|
||||
exit("Could not locate project root above " . __DIR__ . "\n");
|
||||
}
|
||||
|
||||
$settings = require $root . '/config/system/settings.php';
|
||||
$db = $settings['DB']['Connections']['Default'];
|
||||
$pdo = new PDO(
|
||||
sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $db['host'] ?? 'localhost', $db['port'] ?? 3306, $db['dbname'] ?? ''),
|
||||
$db['user'] ?? '',
|
||||
$db['password'] ?? '',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
);
|
||||
|
||||
$stmt = $pdo->prepare('SELECT uid FROM pages WHERE deleted = 0 AND slug = ? AND sys_language_uid IN (0,-1)');
|
||||
$stmt->execute([PAGE_SLUG]);
|
||||
$pid = (int)$stmt->fetchColumn();
|
||||
if ($pid === 0) {
|
||||
exit("Seite " . PAGE_SLUG . " nicht gefunden.\n");
|
||||
}
|
||||
echo ($apply ? 'APPLY' : 'INSPECT') . " · Seite #$pid " . PAGE_SLUG . "\n\n";
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT uid, colPos, sorting, CType, header, tx_container_parent, hidden
|
||||
FROM tt_content WHERE deleted = 0 AND pid = ? ORDER BY colPos, sorting'
|
||||
);
|
||||
$stmt->execute([$pid]);
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$delete = [];
|
||||
printf("%-7s %-7s %-8s %-28s %-9s %-7s %s\n", 'uid', 'colPos', 'parent', 'CType', 'hidden', 'aktion', 'header');
|
||||
foreach ($rows as $row) {
|
||||
$keep = in_array((int)$row['uid'], KEEP, true);
|
||||
if (!$keep) {
|
||||
$delete[] = (int)$row['uid'];
|
||||
}
|
||||
printf("%-7d %-7d %-8d %-28s %-9s %-7s %s\n",
|
||||
$row['uid'], $row['colPos'], $row['tx_container_parent'], $row['CType'],
|
||||
$row['hidden'] ? 'hidden' : '-', $keep ? 'KEEP' : 'DELETE', mb_substr((string)$row['header'], 0, 45));
|
||||
}
|
||||
echo "\n" . count($rows) . " Element(e), davon " . count($delete) . " zu loeschen, " . (count($rows) - count($delete)) . " bleiben.\n";
|
||||
|
||||
$missing = array_diff(KEEP, array_map(static fn(array $r): int => (int)$r['uid'], $rows));
|
||||
if ($missing !== []) {
|
||||
echo "WARNUNG: Whitelist-Elemente fehlen auf der Seite: " . implode(', ', $missing) . " - NICHT anwenden, erst klaeren.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!$apply) {
|
||||
echo "\nInspect only - mit --apply soft-loeschen.\n";
|
||||
exit(0);
|
||||
}
|
||||
if ($delete === []) {
|
||||
echo "Nichts zu tun.\n";
|
||||
exit(0);
|
||||
}
|
||||
$in = implode(',', array_fill(0, count($delete), '?'));
|
||||
$stmt = $pdo->prepare("UPDATE tt_content SET deleted = 1, tstamp = UNIX_TIMESTAMP() WHERE uid IN ($in)");
|
||||
$stmt->execute($delete);
|
||||
echo "Soft-geloescht: " . $stmt->rowCount() . " Element(e). Danach: vendor/bin/typo3 cache:flush\n";
|
||||
@@ -0,0 +1,131 @@
|
||||
#!/bin/bash
|
||||
# fe_comment Isolationstest (2026-11-03): zwei FE-User NACHEINANDER einloggen
|
||||
# und pruefen, dass /api/fe-comment/state UND das USER_INT-Feld auf
|
||||
# /_json/ticket-overview jedem User nur die EIGENEN Tickets zeigen.
|
||||
# Aufruf: bash migrations/fec_isolation_test.sh <user1> <user2>
|
||||
# (fragt beide Passwoerter unsichtbar ab)
|
||||
set -e
|
||||
cd /usr/www/users/vitecevo/live
|
||||
BASE=https://dev.vitec.com
|
||||
|
||||
U1=${1:?Aufruf: fec_isolation_test.sh <user1> <user2>}
|
||||
U2=${2:?Aufruf: fec_isolation_test.sh <user1> <user2>}
|
||||
|
||||
extract_token() {
|
||||
php -r '
|
||||
$j = json_decode(file_get_contents($argv[1]), true);
|
||||
$f = "";
|
||||
$w = function ($n) use (&$w, &$f) {
|
||||
if (is_array($n)) {
|
||||
if (($n["name"] ?? "") === "__RequestToken" && isset($n["value"])) {
|
||||
$f = (string)$n["value"];
|
||||
}
|
||||
foreach ($n as $c) { $w($c); }
|
||||
} elseif (is_string($n) && $f === "") {
|
||||
if (preg_match("/name=\"__RequestToken\" value=\"([^\"]+)\"/", $n, $m)) {
|
||||
$f = $m[1];
|
||||
}
|
||||
}
|
||||
};
|
||||
$w($j);
|
||||
echo $f;
|
||||
' "$1"
|
||||
}
|
||||
|
||||
login() { # $1=user $2=pass $3=jar
|
||||
rm -f "$3"
|
||||
curl -sS -c "$3" -o /tmp/fec_form.json "$BASE/_json/fe-user-profile"
|
||||
TOKEN=$(extract_token /tmp/fec_form.json)
|
||||
if [ -z "$TOKEN" ]; then echo "KEIN __RequestToken gefunden"; exit 1; fi
|
||||
curl -sS -b "$3" -c "$3" -o /tmp/fec_login.json \
|
||||
--data-urlencode "user=$1" \
|
||||
--data-urlencode "pass=$2" \
|
||||
--data-urlencode "logintype=login" \
|
||||
--data-urlencode "pid=520" \
|
||||
--data-urlencode "__RequestToken=$TOKEN" \
|
||||
"$BASE/_json/fe-user-profile"
|
||||
if ! grep -q fe_typo_user "$3"; then
|
||||
echo "LOGIN FEHLGESCHLAGEN fuer $1 (kein fe_typo_user-Cookie)"; exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
summarize_state() { # stdin = state json, $1 = label
|
||||
php -r '
|
||||
$j = json_decode(stream_get_contents(STDIN), true);
|
||||
printf("%-28s allowed=%s user=%s summaryUid=%s items=%d\n",
|
||||
$argv[1],
|
||||
var_export($j["allowed"] ?? null, true),
|
||||
$j["user"]["name"] ?? "-",
|
||||
isset($j["summary"]["uid"]) ? (string)$j["summary"]["uid"] : "-",
|
||||
is_array($j["summary"]["items"] ?? null) ? count($j["summary"]["items"]) : 0);
|
||||
foreach (($j["summary"]["items"] ?? []) as $i) {
|
||||
printf(" item #%d %s\n", $i["uid"], mb_substr($i["url"], 0, 70));
|
||||
}
|
||||
' "$1"
|
||||
}
|
||||
|
||||
summarize_overview() { # stdin = page json, $1 = label
|
||||
php -r '
|
||||
$j = json_decode(stream_get_contents(STDIN), true);
|
||||
$fo = null;
|
||||
foreach (($j["content"]["colPos0"] ?? []) as $el) {
|
||||
if (($el["type"] ?? "") === "fecomment_overview") {
|
||||
$fo = $el["content"]["feedbackOverview"] ?? null;
|
||||
}
|
||||
}
|
||||
if ($fo === null) { echo $argv[1] . " KEIN fecomment_overview-Element gefunden\n"; exit; }
|
||||
printf("%-28s allowed=%s user=%s summaryUid=%s items=%d\n",
|
||||
$argv[1],
|
||||
var_export($fo["allowed"] ?? null, true),
|
||||
$fo["user"]["name"] ?? "-",
|
||||
isset($fo["summary"]["uid"]) ? (string)$fo["summary"]["uid"] : "-",
|
||||
is_array($fo["summary"]["items"] ?? null) ? count($fo["summary"]["items"]) : 0);
|
||||
' "$1"
|
||||
}
|
||||
|
||||
check_user() { # $1=user $2=jar
|
||||
echo "=== $1 ==="
|
||||
curl -sS -b "$2" "$BASE/api/fe-comment/state" | summarize_state "state-API"
|
||||
curl -sS -b "$2" "$BASE/_json/ticket-overview" | summarize_overview "ticket-overview (USER_INT)"
|
||||
echo
|
||||
}
|
||||
|
||||
read -rsp "Passwort fuer $U1: " P1; echo
|
||||
read -rsp "Passwort fuer $U2: " P2; echo
|
||||
|
||||
login "$U1" "$P1" /tmp/fec_u1.jar
|
||||
login "$U2" "$P2" /tmp/fec_u2.jar
|
||||
|
||||
# Testticket als U1 anlegen, damit es etwas zu sehen (bzw. NICHT zu sehen) gibt
|
||||
echo "--- lege Testticket als $U1 an ---"
|
||||
TICKET=$(curl -sS -b /tmp/fec_u1.jar -H 'Content-Type: application/json' \
|
||||
-d '{"url":"/isolation-test","note":"<p>Isolationstest - bitte ignorieren</p>"}' \
|
||||
"$BASE/api/fe-comment/feedback" | php -r '
|
||||
$j = json_decode(stream_get_contents(STDIN), true);
|
||||
echo (int)($j["uid"] ?? 0);')
|
||||
echo "Testticket uid=$TICKET"
|
||||
if [ "$TICKET" = "0" ]; then echo "ANLEGEN FEHLGESCHLAGEN"; exit 1; fi
|
||||
echo
|
||||
|
||||
# beide Sessions leben gleichzeitig - genau wie zwei Browser
|
||||
check_user "$U1" /tmp/fec_u1.jar
|
||||
check_user "$U2" /tmp/fec_u2.jar
|
||||
|
||||
# Gegenprobe: darf U2 das Ticket von U1 veraendern? (erwartet: Fehler)
|
||||
echo "--- Gegenprobe: $U2 versucht Ticket #$TICKET von $U1 zu loeschen ---"
|
||||
curl -sS -b /tmp/fec_u2.jar -X POST "$BASE/api/fe-comment/feedback/$TICKET/delete"; echo
|
||||
echo "(erwartet: {\"error\":\"feedback not found or not yours\"})"
|
||||
echo
|
||||
|
||||
# aufraeumen: U1 loescht sein eigenes Testticket
|
||||
echo "--- aufraeumen: $U1 loescht Testticket #$TICKET ---"
|
||||
curl -sS -b /tmp/fec_u1.jar -X POST "$BASE/api/fe-comment/feedback/$TICKET/delete"; echo
|
||||
echo
|
||||
|
||||
echo "=== anonym (ohne Session) ==="
|
||||
curl -sS "$BASE/api/fe-comment/state" | summarize_state "state-API"
|
||||
curl -sS "$BASE/_json/ticket-overview" | summarize_overview "ticket-overview (USER_INT)"
|
||||
echo
|
||||
echo "ERWARTUNG: $U1 sieht 1 Item (das Testticket), $U2 sieht 0 Items,"
|
||||
echo "die Gegenprobe-Loeschung durch $U2 schlaegt fehl, anonym ist allowed=false."
|
||||
rm -f /tmp/fec_u1.jar /tmp/fec_u2.jar /tmp/fec_form.json /tmp/fec_login.json
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Ticket 0014-FE Punkt 2: doppelt kodierte HTML-Entities in tt_content
|
||||
* (z. B. "VITEC&apos;s" im JSON). Ursache: die Produkt-Importer
|
||||
* escapten mit ENT_QUOTES|ENT_HTML5 (' -> '), die RTE-Persistenz
|
||||
* escapte das & erneut. Die Importer schreiben seit 2026-10-28 literal
|
||||
* (ENT_NOQUOTES); dieses Skript repariert den Bestand um EINE
|
||||
* Escaping-Stufe: &apos; -> ' usw.
|
||||
*
|
||||
* Plain PDO (Muster migrations/check_bg.php).
|
||||
*
|
||||
* Usage: php migrations/fix_double_entities.php (Scan/Report)
|
||||
* php migrations/fix_double_entities.php --apply
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
// Stufe 1: doppelt kodierte Entities um eine Stufe reduzieren.
|
||||
// Stufe 2 (Befund 2026-10-28: Scan fand KEINE Doubles in der DB - die DB
|
||||
// traegt ' EINFACH kodiert, erst die Render-Pipeline escapet das &
|
||||
// erneut): Apostroph-Entities literal machen, dann kann nichts mehr doppeln.
|
||||
const PATTERNS = ['&apos;', '&quot;', '&#039;', '&amp;', '&nbsp;', ''', '''];
|
||||
const REPLACEMENTS = [''', '"', ''', '&', ' ', "'", "'"];
|
||||
|
||||
$apply = in_array('--apply', $argv, true);
|
||||
|
||||
$root = null;
|
||||
$dir = __DIR__;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
if (is_file($dir . '/config/system/settings.php')) {
|
||||
$root = $dir;
|
||||
break;
|
||||
}
|
||||
$dir = dirname($dir);
|
||||
}
|
||||
if ($root === null) {
|
||||
exit("Could not locate project root above " . __DIR__ . "\n");
|
||||
}
|
||||
$settings = require $root . '/config/system/settings.php';
|
||||
$db = $settings['DB']['Connections']['Default'];
|
||||
$pdo = new PDO(
|
||||
sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $db['host'] ?? 'localhost', $db['port'] ?? 3306, $db['dbname'] ?? ''),
|
||||
$db['user'] ?? '',
|
||||
$db['password'] ?? '',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
|
||||
echo ($apply ? 'APPLY' : 'SCAN') . "\n";
|
||||
|
||||
$like = implode(' OR ', array_map(static fn(string $p): string => "bodytext LIKE '%" . $p . "%'", PATTERNS));
|
||||
$rows = $pdo->query(
|
||||
"SELECT c.uid, c.pid, p.slug, c.CType, c.bodytext FROM tt_content c
|
||||
LEFT JOIN pages p ON p.uid = c.pid
|
||||
WHERE c.deleted = 0 AND ($like) ORDER BY p.slug, c.uid"
|
||||
)->fetchAll();
|
||||
|
||||
$fixed = 0;
|
||||
foreach ($rows as $row) {
|
||||
$new = str_replace(PATTERNS, REPLACEMENTS, (string)$row['bodytext']);
|
||||
$hits = [];
|
||||
foreach (PATTERNS as $pattern) {
|
||||
$n = substr_count((string)$row['bodytext'], $pattern);
|
||||
if ($n > 0) {
|
||||
$hits[] = $pattern . 'x' . $n;
|
||||
}
|
||||
}
|
||||
printf("#%-6d %-28s %-45s %s\n", $row['uid'], $row['CType'], (string)$row['slug'], implode(' ', $hits));
|
||||
if ($apply) {
|
||||
$pdo->prepare('UPDATE tt_content SET bodytext = ?, tstamp = UNIX_TIMESTAMP() WHERE uid = ?')
|
||||
->execute([$new, (int)$row['uid']]);
|
||||
$fixed++;
|
||||
}
|
||||
}
|
||||
echo "\n" . count($rows) . " Element(e) betroffen" . ($apply ? ", $fixed repariert. Danach: vendor/bin/typo3 cache:flush" : ". Mit --apply reparieren.") . "\n";
|
||||
if (!$apply && $rows !== []) {
|
||||
echo "HINWEIS: Ersetzt wird genau EINE Escaping-Stufe (&apos; -> ' usw.).\n";
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Ticket 0014-FE, Folge-Befund: pid 569 (/products/graphics-and-multi-display)
|
||||
* nach dem IQS4-Kategorie-Re-Import. Container und ihre Kinder sind intakt,
|
||||
* aber: 8 Duplikat-Karten top-level in colPos0 (11853-11860) und die Grids
|
||||
* stehen falsch relativ zu ihren Intros (11840 vor 11629, 11841 vor 11632).
|
||||
*
|
||||
* Reparatur: Duplikate soft-loeschen, colPos0 in Sektions-Folge neu sortieren.
|
||||
*
|
||||
* Usage: php migrations/fix_graphics_grid.php (inspect)
|
||||
* php migrations/fix_graphics_grid.php --apply
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
const PID = 569;
|
||||
const DUPLICATES = [11853, 11854, 11855, 11856, 11857, 11858, 11859, 11860];
|
||||
// Intro -> Grid(s): Why VITEC -> 11838 (3 Benefit-Karten) + 11839 (1),
|
||||
// Finder-Intro + Comparison-Text, Products-Intro -> 11840 (IGC + IQS4),
|
||||
// Solutions-Intro -> 11841 (2 Solution-Karten)
|
||||
const ORDER = [11620, 11621, 11622, 11838, 11839, 11627, 11628, 11629, 11840, 11632, 11841];
|
||||
|
||||
$apply = in_array('--apply', $argv, true);
|
||||
|
||||
$root = null;
|
||||
$dir = __DIR__;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
if (is_file($dir . '/config/system/settings.php')) {
|
||||
$root = $dir;
|
||||
break;
|
||||
}
|
||||
$dir = dirname($dir);
|
||||
}
|
||||
if ($root === null) {
|
||||
exit("Could not locate project root above " . __DIR__ . "\n");
|
||||
}
|
||||
$settings = require $root . '/config/system/settings.php';
|
||||
$db = $settings['DB']['Connections']['Default'];
|
||||
$pdo = new PDO(
|
||||
sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $db['host'] ?? 'localhost', $db['port'] ?? 3306, $db['dbname'] ?? ''),
|
||||
$db['user'] ?? '',
|
||||
$db['password'] ?? '',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
|
||||
echo ($apply ? 'APPLY' : 'INSPECT') . " · pid " . PID . "\n\n";
|
||||
|
||||
$rows = $pdo->query('SELECT uid, CType, colPos, LEFT(header,40) h FROM tt_content WHERE deleted = 0 AND pid = ' . PID)->fetchAll();
|
||||
$byUid = [];
|
||||
foreach ($rows as $row) {
|
||||
$byUid[(int)$row['uid']] = $row;
|
||||
}
|
||||
foreach (array_merge(ORDER, DUPLICATES) as $uid) {
|
||||
if (!isset($byUid[$uid])) {
|
||||
exit("ABBRUCH: uid $uid fehlt auf der Seite - erst klaeren.\n");
|
||||
}
|
||||
}
|
||||
$tail = [];
|
||||
foreach ($pdo->query('SELECT uid, CType, LEFT(header,40) h FROM tt_content WHERE deleted = 0 AND pid = ' . PID . ' AND colPos = 0 ORDER BY sorting, uid') as $row) {
|
||||
$uid = (int)$row['uid'];
|
||||
if (!in_array($uid, ORDER, true) && !in_array($uid, DUPLICATES, true)) {
|
||||
$tail[] = $uid;
|
||||
echo "tail: #$uid {$row['CType']} \"{$row['h']}\"\n";
|
||||
}
|
||||
}
|
||||
echo "\n1) " . count(DUPLICATES) . " Duplikat-Karten soft-loeschen: " . implode(',', DUPLICATES) . "\n";
|
||||
echo "2) colPos0 neu sortieren:\n";
|
||||
$sequence = array_merge(ORDER, $tail);
|
||||
foreach ($sequence as $i => $uid) {
|
||||
$r = $byUid[$uid];
|
||||
printf(" %3d. #%-6d %-22s %s\n", $i + 1, $uid, $r['CType'], $r['h']);
|
||||
}
|
||||
if (!$apply) {
|
||||
echo "\nInspect only - mit --apply schreiben.\n";
|
||||
exit(0);
|
||||
}
|
||||
$pdo->exec('UPDATE tt_content SET deleted = 1, tstamp = UNIX_TIMESTAMP() WHERE uid IN (' . implode(',', DUPLICATES) . ')');
|
||||
$sorting = 0;
|
||||
$stmt = $pdo->prepare('UPDATE tt_content SET sorting = ?, tstamp = UNIX_TIMESTAMP() WHERE uid = ?');
|
||||
foreach ($sequence as $uid) {
|
||||
$sorting += 256;
|
||||
$stmt->execute([$sorting, $uid]);
|
||||
}
|
||||
echo "\nGeschrieben. Danach: vendor/bin/typo3 cache:flush\n";
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Ticket 0014-FE Punkt 1: /products (pid 447) ohne Grid-Container im Payload.
|
||||
*
|
||||
* Befund (check_grid_447.php, 2026-10-28): der Portfolio-Re-Run (IQS4) hat
|
||||
* die 15 vitec_cols_33_33_33-Container VERSTECKT (hidden=1, uid 11794-11808;
|
||||
* ihre 38 Original-Karten stecken intakt und bebildert darin), 38 neue
|
||||
* Duplikat-Karten top-level in colPos0 angelegt (11861-11898) und die
|
||||
* Sortierung neu verteilt - die versteckten Container stehen dadurch an
|
||||
* falschen Positionen.
|
||||
*
|
||||
* Reparatur: Container einblenden, Duplikate soft-loeschen, colPos0 in der
|
||||
* P5-Reihenfolge (Intro -> Grid(s) -> naechstes Intro, danach Banner/FAQ in
|
||||
* bisheriger Folge) mit Stride 256 neu sortieren.
|
||||
*
|
||||
* Usage: php migrations/fix_products_grid.php (inspect)
|
||||
* php migrations/fix_products_grid.php --apply
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
const PID = 447;
|
||||
const CONTAINERS = [11794, 11795, 11796, 11797, 11798, 11799, 11800, 11801, 11802, 11803, 11804, 11805, 11806, 11807, 11808];
|
||||
const DUPLICATES = [
|
||||
11861, 11862, 11863, 11864, 11865, 11866, 11867, 11868, 11869, 11870,
|
||||
11871, 11872, 11873, 11874, 11875, 11876, 11877, 11878, 11879, 11880,
|
||||
11881, 11882, 11883, 11884, 11885, 11886, 11887, 11888, 11889, 11890,
|
||||
11891, 11892, 11893, 11894, 11895, 11896, 11897, 11898,
|
||||
];
|
||||
// Zielreihenfolge colPos0: Sektions-Intro, danach seine Grids (Zuordnung
|
||||
// ueber die Container-Kinder verifiziert, check_grid_447-Ausgabe 2026-10-28)
|
||||
const ORDER = [
|
||||
11741, 11808, 11807, // IPTV & Digital Signage (5 Karten -> 2 Grids)
|
||||
11747, 11806, 11805, 11804, // Video Encoding & Streaming (7 -> 3)
|
||||
11755, 11803, // Gateways & Transcoding (3 -> 1)
|
||||
11759, 11802, 11801, // Control Room Platforms (6 -> 2)
|
||||
11766, 11800, // AV over IP & KVM (2 -> 1)
|
||||
11769, 11799, 11798, // Video Wall & Display Processing (4 -> 2)
|
||||
11774, 11797, // Graphics & Multi-Display (2 -> 1)
|
||||
11777, 11796, 11795, // Video Capture (6 -> 2)
|
||||
11784, 11794, // Infrastructure & Accessories (3 -> 1)
|
||||
];
|
||||
|
||||
$apply = in_array('--apply', $argv, true);
|
||||
|
||||
$root = null;
|
||||
$dir = __DIR__;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
if (is_file($dir . '/config/system/settings.php')) {
|
||||
$root = $dir;
|
||||
break;
|
||||
}
|
||||
$dir = dirname($dir);
|
||||
}
|
||||
if ($root === null) {
|
||||
exit("Could not locate project root above " . __DIR__ . "\n");
|
||||
}
|
||||
$settings = require $root . '/config/system/settings.php';
|
||||
$db = $settings['DB']['Connections']['Default'];
|
||||
$pdo = new PDO(
|
||||
sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $db['host'] ?? 'localhost', $db['port'] ?? 3306, $db['dbname'] ?? ''),
|
||||
$db['user'] ?? '',
|
||||
$db['password'] ?? '',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
|
||||
echo ($apply ? 'APPLY' : 'INSPECT') . " · pid " . PID . "\n\n";
|
||||
|
||||
// Sanity: alle ORDER-/CONTAINER-uids existieren undeleted auf der Seite
|
||||
$rows = $pdo->query('SELECT uid, CType, colPos, hidden, LEFT(header,40) h FROM tt_content WHERE deleted = 0 AND pid = ' . PID)->fetchAll();
|
||||
$byUid = [];
|
||||
foreach ($rows as $row) {
|
||||
$byUid[(int)$row['uid']] = $row;
|
||||
}
|
||||
foreach (array_merge(ORDER, DUPLICATES) as $uid) {
|
||||
if (!isset($byUid[$uid])) {
|
||||
exit("ABBRUCH: uid $uid fehlt auf der Seite (geloescht/verschoben?) - erst klaeren.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Rest von colPos0 (Banner, FAQ, ...) hinten anhaengen, bisherige Folge
|
||||
$tail = [];
|
||||
foreach ($pdo->query('SELECT uid, CType, LEFT(header,40) h FROM tt_content WHERE deleted = 0 AND pid = ' . PID . ' AND colPos = 0 ORDER BY sorting, uid') as $row) {
|
||||
$uid = (int)$row['uid'];
|
||||
if (!in_array($uid, ORDER, true) && !in_array($uid, DUPLICATES, true)) {
|
||||
$tail[] = $uid;
|
||||
echo "tail: #$uid {$row['CType']} \"{$row['h']}\"\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n1) " . count(CONTAINERS) . " Container einblenden (hidden=0): " . implode(',', CONTAINERS) . "\n";
|
||||
echo "2) " . count(DUPLICATES) . " Duplikat-Karten soft-loeschen: " . implode(',', DUPLICATES) . "\n";
|
||||
echo "3) colPos0 neu sortieren: " . count(ORDER) . " Elemente in P5-Folge + " . count($tail) . " Tail-Element(e)\n";
|
||||
$sequence = array_merge(ORDER, $tail);
|
||||
foreach ($sequence as $i => $uid) {
|
||||
$r = $byUid[$uid];
|
||||
printf(" %3d. #%-6d %-22s %s\n", $i + 1, $uid, $r['CType'], $r['h']);
|
||||
}
|
||||
|
||||
if (!$apply) {
|
||||
echo "\nInspect only - mit --apply schreiben.\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$pdo->exec('UPDATE tt_content SET hidden = 0, tstamp = UNIX_TIMESTAMP() WHERE uid IN (' . implode(',', CONTAINERS) . ')');
|
||||
$pdo->exec('UPDATE tt_content SET deleted = 1, tstamp = UNIX_TIMESTAMP() WHERE uid IN (' . implode(',', DUPLICATES) . ')');
|
||||
$sorting = 0;
|
||||
$stmt = $pdo->prepare('UPDATE tt_content SET sorting = ?, tstamp = UNIX_TIMESTAMP() WHERE uid = ?');
|
||||
foreach ($sequence as $uid) {
|
||||
$sorting += 256;
|
||||
$stmt->execute([$sorting, $uid]);
|
||||
}
|
||||
echo "\nGeschrieben. Danach: vendor/bin/typo3 cache:flush\n";
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Ticket 0017-FE Punkt 1 + 2 (Altbestand vitec.com-Import, gegen das
|
||||
* Original auf www.vitec.com verifiziert, 2026-10-29):
|
||||
*
|
||||
* 1) CDC (centers-for-disease-control-cdc): die Background-vitec_columns
|
||||
* (#355) traegt links den Text des Crowne Plaza Muscat OCEC und rechts
|
||||
* dessen Logo. Das Original hat dort CDC-Copy. -> linkes Text-Item auf
|
||||
* die Original-Absaetze setzen, rechtes Logo-Item soft-loeschen
|
||||
* (CDC-Bild kann redaktionell nachgelegt werden).
|
||||
*
|
||||
* 2) "Key Feature"-textmedia mit identischem Hotel-/Signage-Text in 5
|
||||
* Stories. Abgleich mit dem Original:
|
||||
* - parramatta-eels #218: Text ist DORT ECHT -> behalten
|
||||
* - belgrade #253, isr #266, meydan #206: Original hat KEIN
|
||||
* Key Feature -> Element soft-loeschen
|
||||
* - proteo #234: Original hat MIL-spec-Bullets -> bodytext ersetzen
|
||||
*
|
||||
* Usage: php migrations/fix_story_content_errors.php (inspect)
|
||||
* php migrations/fix_story_content_errors.php --apply
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
const CDC_COLUMNS_UID = 355;
|
||||
const DELETE_ELEMENTS = [253, 266, 206];
|
||||
const PROTEO_UID = 234;
|
||||
const KEEP_CHECK = 218;
|
||||
|
||||
const CDC_BACKGROUND = '<p>When it comes mitigating major threats to public health, it is imperative for organizations like the Centers for Disease Control (CDC) to keep their finger on the pulse of global developments that require a coordinated national or global response.</p>'
|
||||
. '<p>In addition to being ardent consumers of news and information, doctors, scientists, and other healthcare experts at the CDC produce a tremendous amount of original content for both internal and external consumption. This makes the agency one of the most video-centric departments in the federal government.</p>';
|
||||
|
||||
const PROTEO_KEYFEATURE = '<ul>'
|
||||
. '<li>MIL-spec ruggedization HEVC & H.264 Encoder</li>'
|
||||
. '<li>Low latency HD/SD Streaming</li>'
|
||||
. '<li>Low bandwidth HEVC Streaming</li>'
|
||||
. '<li>Overlay for video input identification and timestamping</li>'
|
||||
. '<li>KLV / STANAG metadata processing for ISR</li>'
|
||||
. '</ul>';
|
||||
|
||||
$apply = in_array('--apply', $argv, true);
|
||||
|
||||
$root = null;
|
||||
$dir = __DIR__;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
if (is_file($dir . '/config/system/settings.php')) {
|
||||
$root = $dir;
|
||||
break;
|
||||
}
|
||||
$dir = dirname($dir);
|
||||
}
|
||||
if ($root === null) {
|
||||
exit("Could not locate project root above " . __DIR__ . "\n");
|
||||
}
|
||||
$settings = require $root . '/config/system/settings.php';
|
||||
$db = $settings['DB']['Connections']['Default'];
|
||||
$pdo = new PDO(
|
||||
sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $db['host'] ?? 'localhost', $db['port'] ?? 3306, $db['dbname'] ?? ''),
|
||||
$db['user'] ?? '',
|
||||
$db['password'] ?? '',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
|
||||
echo ($apply ? 'APPLY' : 'INSPECT') . "\n";
|
||||
|
||||
// --- 1) CDC-Spalten-Items ----------------------------------------------------
|
||||
// Collection-Tabellen generisch ueber foreign_table_parent_uid finden
|
||||
// (die Content-Blocks-Felder heissen hier unprefixed: column/text/item_type)
|
||||
$itemTables = $pdo->query(
|
||||
"SELECT DISTINCT TABLE_NAME FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND COLUMN_NAME = 'foreign_table_parent_uid'"
|
||||
)->fetchAll(PDO::FETCH_COLUMN);
|
||||
echo "\n--- 1) CDC #" . CDC_COLUMNS_UID . " · Kandidaten-Tabellen: " . implode(', ', $itemTables) . "\n";
|
||||
$fixedLeft = false;
|
||||
$deletedRight = false;
|
||||
foreach ($itemTables as $table) {
|
||||
$cols = $pdo->query("SELECT COLUMN_NAME FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = " . $pdo->quote($table))->fetchAll(PDO::FETCH_COLUMN);
|
||||
$textCol = in_array('vitec_text', $cols, true) ? 'vitec_text' : (in_array('text', $cols, true) ? 'text' : null);
|
||||
$colCol = in_array('vitec_column', $cols, true) ? 'vitec_column' : (in_array('column', $cols, true) ? 'column' : null);
|
||||
if ($textCol === null || $colCol === null) {
|
||||
continue;
|
||||
}
|
||||
$items = $pdo->query("SELECT * FROM `$table` WHERE deleted = 0 AND foreign_table_parent_uid = " . CDC_COLUMNS_UID . " ORDER BY sorting")->fetchAll();
|
||||
if ($items === []) {
|
||||
continue;
|
||||
}
|
||||
echo " Tabelle: $table (textCol=$textCol, colCol=$colCol)\n";
|
||||
foreach ($items as $item) {
|
||||
$text = (string)$item[$textCol];
|
||||
printf(" item #%d col=%s type=%s text=\"%s\"\n",
|
||||
$item['uid'], $item[$colCol] ?? '?',
|
||||
$item['vitec_item_type'] ?? ($item['item_type'] ?? '?'),
|
||||
mb_substr(trim(strip_tags($text)), 0, 70));
|
||||
$isHotelText = str_contains($text, 'Crowne Plaza');
|
||||
$column = (string)($item[$colCol] ?? 'left');
|
||||
if ($isHotelText && $textCol !== null) {
|
||||
echo " -> linkes Text-Item: Hotel-Text wird durch CDC-Background ersetzt\n";
|
||||
if ($apply) {
|
||||
$pdo->prepare("UPDATE `$table` SET `$textCol` = ?, tstamp = UNIX_TIMESTAMP() WHERE uid = ?")
|
||||
->execute([CDC_BACKGROUND, (int)$item['uid']]);
|
||||
$fixedLeft = true;
|
||||
}
|
||||
} elseif ($column === 'right') {
|
||||
echo " -> rechtes Item (Hotel-Logo): wird soft-geloescht\n";
|
||||
if ($apply) {
|
||||
$pdo->prepare("UPDATE `$table` SET deleted = 1, tstamp = UNIX_TIMESTAMP() WHERE uid = ?")
|
||||
->execute([(int)$item['uid']]);
|
||||
$deletedRight = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break; // erste Tabelle mit Items reicht
|
||||
}
|
||||
if ($apply) {
|
||||
echo $fixedLeft ? " Text ersetzt.\n" : " WARNUNG: kein Hotel-Text-Item gefunden!\n";
|
||||
echo $deletedRight ? " Logo-Item geloescht.\n" : " Hinweis: kein rechtes Item geloescht.\n";
|
||||
}
|
||||
|
||||
// --- 2) Key-Feature-Elemente -------------------------------------------------
|
||||
echo "\n--- 2) Key-Feature-Elemente ---\n";
|
||||
$in = implode(',', array_merge(DELETE_ELEMENTS, [PROTEO_UID, KEEP_CHECK]));
|
||||
foreach ($pdo->query("SELECT uid, CType, LEFT(header,30) h, LEFT(bodytext,70) b, deleted FROM tt_content WHERE uid IN ($in)") as $row) {
|
||||
$uid = (int)$row['uid'];
|
||||
$action = in_array($uid, DELETE_ELEMENTS, true) ? 'LOESCHEN (Original hat kein Key Feature)'
|
||||
: ($uid === PROTEO_UID ? 'bodytext -> MIL-spec-Bullets (Original)' : 'BEHALTEN (Text im Original echt)');
|
||||
printf(" #%-4d %-12s \"%s\" \"%s\"%s -> %s\n", $uid, $row['CType'], $row['h'],
|
||||
trim(strip_tags((string)$row['b'])), $row['deleted'] ? ' [DELETED]' : '', $action);
|
||||
}
|
||||
if ($apply) {
|
||||
$pdo->exec('UPDATE tt_content SET deleted = 1, tstamp = UNIX_TIMESTAMP() WHERE uid IN (' . implode(',', DELETE_ELEMENTS) . ')');
|
||||
$pdo->prepare('UPDATE tt_content SET bodytext = ?, tstamp = UNIX_TIMESTAMP() WHERE uid = ?')
|
||||
->execute([PROTEO_KEYFEATURE, PROTEO_UID]);
|
||||
echo " 3 geloescht, Proteo ersetzt.\n";
|
||||
}
|
||||
|
||||
echo $apply ? "\nFertig. Danach: vendor/bin/typo3 cache:flush\n" : "\nInspect only - mit --apply schreiben.\n";
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Ticket 0017-FE Punkt 4: drei Alt-Stories tragen Platzhalter in
|
||||
* quote/quote_person/quote_role (africa-hall: "Here Fu Manchu lalala.",
|
||||
* bayer-uk: "This is a testimonial", meydan-racecourse: der Feldhilfetext).
|
||||
* Auf Testimonial-Karten wuerden die erscheinen -> Felder leeren.
|
||||
*
|
||||
* Usage: php migrations/fix_story_placeholder_quotes.php (inspect)
|
||||
* php migrations/fix_story_placeholder_quotes.php --apply
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
const SLUGS = ['africa-hall', 'bayer-uk', 'meydan-racecourse'];
|
||||
|
||||
$apply = in_array('--apply', $argv, true);
|
||||
|
||||
$root = null;
|
||||
$dir = __DIR__;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
if (is_file($dir . '/config/system/settings.php')) {
|
||||
$root = $dir;
|
||||
break;
|
||||
}
|
||||
$dir = dirname($dir);
|
||||
}
|
||||
if ($root === null) {
|
||||
exit("Could not locate project root above " . __DIR__ . "\n");
|
||||
}
|
||||
$settings = require $root . '/config/system/settings.php';
|
||||
$db = $settings['DB']['Connections']['Default'];
|
||||
$pdo = new PDO(
|
||||
sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $db['host'] ?? 'localhost', $db['port'] ?? 3306, $db['dbname'] ?? ''),
|
||||
$db['user'] ?? '',
|
||||
$db['password'] ?? '',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
|
||||
echo ($apply ? 'APPLY' : 'INSPECT') . "\n";
|
||||
$in = implode(',', array_fill(0, count(SLUGS), '?'));
|
||||
$stmt = $pdo->prepare("SELECT uid, slug, LEFT(quote,60) q, quote_person, quote_role
|
||||
FROM tx_vitec_domain_model_usecase WHERE deleted = 0 AND slug IN ($in)");
|
||||
$stmt->execute(SLUGS);
|
||||
$rows = $stmt->fetchAll();
|
||||
foreach ($rows as $row) {
|
||||
printf("#%-4d %-20s quote=\"%s\" person=\"%s\" role=\"%s\" -> leeren\n",
|
||||
$row['uid'], $row['slug'], $row['q'], $row['quote_person'], $row['quote_role']);
|
||||
if ($apply) {
|
||||
$pdo->prepare("UPDATE tx_vitec_domain_model_usecase
|
||||
SET quote = '', quote_person = '', quote_role = '', tstamp = UNIX_TIMESTAMP() WHERE uid = ?")
|
||||
->execute([(int)$row['uid']]);
|
||||
}
|
||||
}
|
||||
echo count($rows) . " Record(s)" . ($apply ? " geleert. Danach cache:flush." : ". Mit --apply leeren.") . "\n";
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Ticket 0017-FE Punkt 5b: Videos als echte vitec_video-Elemente.
|
||||
*
|
||||
* - betfred-hq: "The Result" endet mit "<p>Case Study Video</p>" + Link
|
||||
* auf youtube nw52kbYpF3A -> Absaetze strippen, vitec_video danach.
|
||||
* - kanha-shanti-vanam: "The Result" traegt "<p>Short Videos</p><ul>"
|
||||
* mit zwei YouTube-Links (WD0dHmqN4s0, C8FzHR5aUAI) -> strippen,
|
||||
* zwei vitec_video danach.
|
||||
* - atlanta-hawks: html-Element #326 traegt den escapten iframe
|
||||
* (-ds3p9jb5F0) -> Element loeschen, vitec_video an seiner Position.
|
||||
*
|
||||
* Einfuegung direkt per SQL (pid/colPos 999/tx_vitec_usecase_content/
|
||||
* sorting zwischen den Nachbarn); der Inline-Zaehler content_elements am
|
||||
* Usecase-Record wird mitgezogen.
|
||||
*
|
||||
* Usage: php migrations/fix_story_videos.php (inspect)
|
||||
* php migrations/fix_story_videos.php --apply
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
$apply = in_array('--apply', $argv, true);
|
||||
|
||||
$root = null;
|
||||
$dir = __DIR__;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
if (is_file($dir . '/config/system/settings.php')) {
|
||||
$root = $dir;
|
||||
break;
|
||||
}
|
||||
$dir = dirname($dir);
|
||||
}
|
||||
if ($root === null) {
|
||||
exit("Could not locate project root above " . __DIR__ . "\n");
|
||||
}
|
||||
$settings = require $root . '/config/system/settings.php';
|
||||
$db = $settings['DB']['Connections']['Default'];
|
||||
$pdo = new PDO(
|
||||
sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $db['host'] ?? 'localhost', $db['port'] ?? 3306, $db['dbname'] ?? ''),
|
||||
$db['user'] ?? '',
|
||||
$db['password'] ?? '',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
|
||||
echo ($apply ? 'APPLY' : 'INSPECT') . "\n";
|
||||
|
||||
$col = $pdo->query("SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'tt_content' AND COLUMN_NAME = 'vitec_youtube_code'")->fetchColumn();
|
||||
if ((int)$col !== 1) {
|
||||
exit("ABBRUCH: Spalte tt_content.vitec_youtube_code nicht gefunden.\n");
|
||||
}
|
||||
|
||||
function storyUid(PDO $pdo, string $slug): int
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT uid FROM tx_vitec_domain_model_usecase WHERE deleted = 0 AND slug = ?');
|
||||
$stmt->execute([$slug]);
|
||||
return (int)$stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/** sorting-Wert direkt nach $anchorUid; bei zu kleinem Abstand werden die Kinder vorher mit Stride 256 neu nummeriert. */
|
||||
function sortingAfter(PDO $pdo, int $storyUid, int $anchorUid, bool $apply): int
|
||||
{
|
||||
$children = $pdo->query("SELECT uid, sorting FROM tt_content
|
||||
WHERE deleted = 0 AND tx_vitec_usecase_content = $storyUid ORDER BY sorting, uid")->fetchAll();
|
||||
$index = array_search($anchorUid, array_map(static fn(array $r): int => (int)$r['uid'], $children), true);
|
||||
if ($index === false) {
|
||||
throw new RuntimeException("Anker #$anchorUid nicht unter den Kindern von Story #$storyUid");
|
||||
}
|
||||
$anchorSort = (int)$children[$index]['sorting'];
|
||||
$nextSort = isset($children[$index + 1]) ? (int)$children[$index + 1]['sorting'] : $anchorSort + 512;
|
||||
if ($nextSort - $anchorSort >= 4) {
|
||||
return $anchorSort + intdiv($nextSort - $anchorSort, 2);
|
||||
}
|
||||
// zu eng: komplett neu nummerieren und erneut rechnen
|
||||
if ($apply) {
|
||||
$sorting = 0;
|
||||
$stmt = $pdo->prepare('UPDATE tt_content SET sorting = ? WHERE uid = ?');
|
||||
foreach ($children as $child) {
|
||||
$sorting += 256;
|
||||
$stmt->execute([$sorting, (int)$child['uid']]);
|
||||
}
|
||||
}
|
||||
return ($index + 1) * 256 + 128;
|
||||
}
|
||||
|
||||
function insertVideo(PDO $pdo, int $storyUid, int $pid, int $sorting, string $header, string $code, bool $apply): void
|
||||
{
|
||||
printf(" + vitec_video \"%s\" code=%s sorting=%d\n", $header, $code, $sorting);
|
||||
if (!$apply) {
|
||||
return;
|
||||
}
|
||||
$pdo->prepare("INSERT INTO tt_content
|
||||
(pid, CType, colPos, tx_vitec_usecase_content, sorting, header, vitec_youtube_code, crdate, tstamp)
|
||||
VALUES (?, 'vitec_video', 999, ?, ?, ?, ?, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())")
|
||||
->execute([$pid, $storyUid, $sorting, $header, $code]);
|
||||
$pdo->exec("UPDATE tx_vitec_domain_model_usecase
|
||||
SET content_elements = (SELECT COUNT(*) FROM tt_content WHERE deleted = 0 AND tx_vitec_usecase_content = $storyUid),
|
||||
tstamp = UNIX_TIMESTAMP()
|
||||
WHERE uid = $storyUid");
|
||||
}
|
||||
|
||||
function stripFromBody(PDO $pdo, int $elementUid, string $pattern, bool $apply): bool
|
||||
{
|
||||
$body = (string)$pdo->query("SELECT bodytext FROM tt_content WHERE uid = $elementUid")->fetchColumn();
|
||||
$clean = (string)preg_replace($pattern, '', $body);
|
||||
if ($clean === $body) {
|
||||
echo " ! Strip-Muster traf NICHT in #$elementUid - nichts geaendert\n";
|
||||
return false;
|
||||
}
|
||||
echo " ~ #$elementUid: Video-Absaetze aus bodytext entfernt\n";
|
||||
if ($apply) {
|
||||
$pdo->prepare('UPDATE tt_content SET bodytext = ?, tstamp = UNIX_TIMESTAMP() WHERE uid = ?')
|
||||
->execute([trim($clean), $elementUid]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Betfred -----------------------------------------------------------------
|
||||
echo "\n--- betfred-hq ---\n";
|
||||
$uid = storyUid($pdo, 'betfred-hq');
|
||||
$pid = (int)$pdo->query("SELECT pid FROM tx_vitec_domain_model_usecase WHERE uid = $uid")->fetchColumn();
|
||||
if (stripFromBody($pdo, 11910, '#<p>\s*Case Study Video\s*</p>\s*<p>[^<]*nw52kbYpF3A[^<]*</p>#u', $apply)) {
|
||||
insertVideo($pdo, $uid, $pid, sortingAfter($pdo, $uid, 11910, $apply), 'Case Study Video', 'nw52kbYpF3A', $apply);
|
||||
}
|
||||
|
||||
// --- Kanha Shanti Vanam --------------------------------------------------------
|
||||
echo "\n--- kanha-shanti-vanam ---\n";
|
||||
$uid = storyUid($pdo, 'kanha-shanti-vanam-meditation-center');
|
||||
$pid = (int)$pdo->query("SELECT pid FROM tx_vitec_domain_model_usecase WHERE uid = $uid")->fetchColumn();
|
||||
if (stripFromBody($pdo, 11964, '#<p>\s*Short Videos\s*</p>\s*<ul>\s*<li>[^<]*WD0dHmqN4s0[^<]*</li>\s*<li>[^<]*C8FzHR5aUAI[^<]*</li>\s*</ul>#u', $apply)) {
|
||||
$sorting = sortingAfter($pdo, $uid, 11964, $apply);
|
||||
insertVideo($pdo, $uid, $pid, $sorting, 'Short Videos', 'WD0dHmqN4s0', $apply);
|
||||
insertVideo($pdo, $uid, $pid, $sorting + 1, '', 'C8FzHR5aUAI', $apply);
|
||||
}
|
||||
|
||||
// --- Atlanta Hawks --------------------------------------------------------------
|
||||
echo "\n--- atlanta-hawks ---\n";
|
||||
$uid = storyUid($pdo, 'atlanta-hawks');
|
||||
$old = $pdo->query('SELECT uid, pid, sorting, bodytext FROM tt_content WHERE uid = 326 AND deleted = 0')->fetch();
|
||||
if ($old && str_contains((string)$old['bodytext'], '-ds3p9jb5F0')) {
|
||||
echo " ~ html #326 (escapter iframe) wird geloescht, vitec_video an seiner Position\n";
|
||||
if ($apply) {
|
||||
$pdo->exec('UPDATE tt_content SET deleted = 1, tstamp = UNIX_TIMESTAMP() WHERE uid = 326');
|
||||
}
|
||||
insertVideo($pdo, $uid, (int)$old['pid'], (int)$old['sorting'], '', '-ds3p9jb5F0', $apply);
|
||||
} else {
|
||||
echo " ! html #326 nicht gefunden oder ohne erwarteten iframe - uebersprungen\n";
|
||||
}
|
||||
|
||||
echo $apply ? "\nFertig. Danach: vendor/bin/typo3 cache:flush\n" : "\nInspect only - mit --apply schreiben.\n";
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Zwei Quick Wins aus Arnes Tickets (2026-10-28):
|
||||
*
|
||||
* A) 0012-FE Punkt 2: die Karte "Request access" auf /support (uid 11489)
|
||||
* soll auf /support/login-to-helpdesk#form-helpdesk zeigen (Anker auf
|
||||
* das HelpDesk-Formular; Schema form-<formKey>).
|
||||
*
|
||||
* B) 0009-FE Punkt 7: vitec_ctabanner auf Market-/Solution-Seiten haben
|
||||
* Labels ("Contact our team" / "Request a demo"), aber keine Links.
|
||||
* Leere primary_cta mit Contact-Label -> /contact, leere secondary_cta
|
||||
* mit Demo-Label -> /contact/request-a-demo (als t3://page?uid=...).
|
||||
* Gefuellte Links werden NIE angefasst.
|
||||
*
|
||||
* Plain PDO (Muster migrations/check_bg.php).
|
||||
*
|
||||
* Usage: php migrations/ticket_quickwins_20261028.php (inspect)
|
||||
* php migrations/ticket_quickwins_20261028.php --apply
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
const REQUEST_ACCESS_UID = 11489;
|
||||
const ANCHOR = 'form-helpdesk';
|
||||
|
||||
$apply = in_array('--apply', $argv, true);
|
||||
|
||||
$root = null;
|
||||
$dir = __DIR__;
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
if (is_file($dir . '/config/system/settings.php')) {
|
||||
$root = $dir;
|
||||
break;
|
||||
}
|
||||
$dir = dirname($dir);
|
||||
}
|
||||
if ($root === null) {
|
||||
exit("Could not locate project root above " . __DIR__ . "\n");
|
||||
}
|
||||
|
||||
$settings = require $root . '/config/system/settings.php';
|
||||
$db = $settings['DB']['Connections']['Default'];
|
||||
$pdo = new PDO(
|
||||
sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $db['host'] ?? 'localhost', $db['port'] ?? 3306, $db['dbname'] ?? ''),
|
||||
$db['user'] ?? '',
|
||||
$db['password'] ?? '',
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
);
|
||||
|
||||
echo ($apply ? 'APPLY' : 'INSPECT') . "\n";
|
||||
|
||||
function pageUidBySlug(PDO $pdo, string $slug): int
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT uid FROM pages WHERE deleted = 0 AND slug = ? AND sys_language_uid IN (0,-1)');
|
||||
$stmt->execute([$slug]);
|
||||
return (int)$stmt->fetchColumn();
|
||||
}
|
||||
|
||||
// --- A) Request-access-Karte -> Anker ---------------------------------------
|
||||
echo "\n--- A) Request access -> #" . ANCHOR . " ---\n";
|
||||
$stmt = $pdo->prepare('SELECT uid, header, vitec_primary_cta FROM tt_content WHERE deleted = 0 AND uid = ?');
|
||||
$stmt->execute([REQUEST_ACCESS_UID]);
|
||||
$card = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$card) {
|
||||
echo "Karte #" . REQUEST_ACCESS_UID . " nicht gefunden - uebersprungen.\n";
|
||||
} elseif (stripos((string)$card['header'], 'request access') === false) {
|
||||
echo "Karte #" . REQUEST_ACCESS_UID . " heisst \"" . $card['header'] . "\" (erwartet: Request access) - NICHT angefasst.\n";
|
||||
} else {
|
||||
$cta = (string)$card['vitec_primary_cta'];
|
||||
if (str_contains($cta, '#' . ANCHOR)) {
|
||||
echo "Karte #" . REQUEST_ACCESS_UID . ": Anker bereits gesetzt ($cta) - nichts zu tun.\n";
|
||||
} elseif ($cta === '') {
|
||||
echo "Karte #" . REQUEST_ACCESS_UID . ": primary_cta ist LEER - erst klaeren, nicht angefasst.\n";
|
||||
} else {
|
||||
$new = $cta . '#' . ANCHOR;
|
||||
echo "Karte #" . REQUEST_ACCESS_UID . " (\"" . $card['header'] . "\"): \"$cta\" -> \"$new\"\n";
|
||||
if ($apply) {
|
||||
$pdo->prepare('UPDATE tt_content SET vitec_primary_cta = ?, tstamp = UNIX_TIMESTAMP() WHERE uid = ?')
|
||||
->execute([$new, REQUEST_ACCESS_UID]);
|
||||
echo " geschrieben.\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- B) CTA-Banner-Defaults auf Market-/Solution-Seiten ----------------------
|
||||
echo "\n--- B) ctabanner-Links auf /markets + /solutions ---\n";
|
||||
$contactUid = pageUidBySlug($pdo, '/contact');
|
||||
$demoUid = pageUidBySlug($pdo, '/contact/request-a-demo');
|
||||
$solutionsUid = pageUidBySlug($pdo, '/solutions');
|
||||
if ($contactUid === 0 || $demoUid === 0 || $solutionsUid === 0) {
|
||||
exit("Zielseiten nicht aufloesbar (/contact=#$contactUid, request-a-demo=#$demoUid, /solutions=#$solutionsUid) - Abbruch.\n");
|
||||
}
|
||||
echo "Ziele: /contact=#$contactUid, request-a-demo=#$demoUid, /solutions=#$solutionsUid\n";
|
||||
|
||||
$rows = $pdo->query(
|
||||
"SELECT c.uid, p.slug, c.vitec_primary_cta, c.vitec_primary_cta_label,
|
||||
c.vitec_secondary_cta, c.vitec_secondary_cta_label
|
||||
FROM tt_content c JOIN pages p ON p.uid = c.pid AND p.deleted = 0
|
||||
WHERE c.deleted = 0 AND c.CType = 'vitec_ctabanner'
|
||||
AND (p.slug = '/markets' OR p.slug LIKE '/markets/%' OR p.slug = '/solutions' OR p.slug LIKE '/solutions/%')
|
||||
ORDER BY p.slug"
|
||||
)->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$setPrimary = 0;
|
||||
$setSecondary = 0;
|
||||
foreach ($rows as $row) {
|
||||
$actions = [];
|
||||
$pLabel = (string)$row['vitec_primary_cta_label'];
|
||||
$sLabel = (string)$row['vitec_secondary_cta_label'];
|
||||
// Sub-Solution-Variante: "Contact our team" / "Request a demo";
|
||||
// Market-Variante (Fund 28.10.): "Talk to our team" / "Explore solutions"
|
||||
if ((string)$row['vitec_primary_cta'] === '' && (stripos($pLabel, 'contact') !== false || stripos($pLabel, 'talk') !== false)) {
|
||||
$actions['vitec_primary_cta'] = 't3://page?uid=' . $contactUid;
|
||||
$setPrimary++;
|
||||
}
|
||||
if ((string)$row['vitec_secondary_cta'] === '') {
|
||||
if (stripos($sLabel, 'demo') !== false) {
|
||||
$actions['vitec_secondary_cta'] = 't3://page?uid=' . $demoUid;
|
||||
$setSecondary++;
|
||||
} elseif (stripos($sLabel, 'solution') !== false) {
|
||||
$actions['vitec_secondary_cta'] = 't3://page?uid=' . $solutionsUid;
|
||||
$setSecondary++;
|
||||
}
|
||||
}
|
||||
if ($actions === []) {
|
||||
continue;
|
||||
}
|
||||
echo "#" . $row['uid'] . " " . $row['slug'] . ": " . implode(', ', array_map(
|
||||
static fn(string $field, string $value): string => $field . ' = ' . $value,
|
||||
array_keys($actions), $actions
|
||||
)) . "\n";
|
||||
if ($apply) {
|
||||
$set = implode(', ', array_map(static fn(string $f): string => "$f = ?", array_keys($actions)));
|
||||
$pdo->prepare("UPDATE tt_content SET $set, tstamp = UNIX_TIMESTAMP() WHERE uid = ?")
|
||||
->execute([...array_values($actions), (int)$row['uid']]);
|
||||
}
|
||||
}
|
||||
echo "\n" . count($rows) . " Banner geprueft · $setPrimary primary + $setSecondary secondary " . ($apply ? 'gesetzt' : 'zu setzen') . ".\n";
|
||||
if (!$apply) {
|
||||
echo "Inspect only - mit --apply schreiben.\n";
|
||||
} else {
|
||||
echo "Danach: vendor/bin/typo3 cache:flush\n";
|
||||
}
|
||||
Reference in new issue
Block a user