PNG IHDR wSȚ -tEXtComment Nxplo
| Server IP : 144.76.1.235 / Your IP : 216.73.216.244 Web Server : Apache/2.4.52 (Ubuntu) System : Linux einkaufsring.com 5.15.0-179-generic #189-Ubuntu SMP Tue May 5 18:20:56 UTC 2026 x86_64 User : www-data ( 33) PHP Version : 8.3.16 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /var/www/shop.einkaufsring.com/ |
Upload File : |
# Save the script
cat > /var/www/shop.einkaufsring.com/sanitize_keep_visibility.py <<'PY'
import csv, sys, re, unicodedata, os
SRC = "/var/www/shop.einkaufsring.com/var/import/products/Artikelexport.csv"
DST = "/tmp/Artikelexport_sanitized_with_visibility.csv"
# --- helpers ---
def strip_bom_bytes(b: bytes) -> bytes:
return b[3:] if b.startswith(b'\xef\xbb\xbf') else b
def norm_space(s: str) -> str:
# normalize unicode spaces & trim quotes/space
s = ''.join(' ' if unicodedata.category(c) in ('Zs','Zl','Zp') else c for c in s)
s = re.sub(r'[\u0000-\u001F\u007F]', '', s)
return s.strip(' \t\n\r\x0b\x0c"')
def looks_like_header(row_lower, header_lower):
needles = ['sku','name','price','visibility','status','product_online','url_key']
matches = 0
for n in needles:
try:
pos = header_lower.index(n)
if pos < len(row_lower) and row_lower[pos] == n:
matches += 1
except ValueError:
pass
return matches >= 3
def slugify(s: str) -> str:
if not s: return "item"
t = unicodedata.normalize('NFKD', s)
t = t.encode('ascii','ignore').decode('ascii')
t = re.sub(r'[^a-zA-Z0-9]+','-', t).strip('-').lower()
return t or 'item'
# Canonical map
CANON = {
'1': 'Not Visible Individually',
'2': 'Catalog',
'3': 'Search',
'4': 'Catalog, Search',
}
LABELS = {v.lower(): v for v in CANON.values()}
ALIASES = {
'not visible': 'Not Visible Individually',
'not visible individually': 'Not Visible Individually',
'nicht sichtbar': 'Not Visible Individually',
'nicht einzeln sichtbar': 'Not Visible Individually',
'catalog': 'Catalog',
'katalog': 'Catalog',
'search': 'Search',
'suche': 'Search',
'catalog, search': 'Catalog, Search',
'catalog , search': 'Catalog, Search',
'catalog search': 'Catalog, Search',
'katalog, suche': 'Catalog, Search',
'both': 'Catalog, Search',
}
def normalize_visibility(v: str) -> str:
if v is None: v = ''
raw = v.strip()
if raw in CANON: # '1'..'4'
return CANON[raw]
low = re.sub(r'\s+', ' ', raw.lower())
if low in LABELS:
return LABELS[low]
if low in ALIASES:
return ALIASES[low]
# tolerate "Catalog,Search" (no space) or extra spaces
low2 = low.replace(', ', ',').replace(' ,', ',')
if low2 == 'catalog,search':
return 'Catalog, Search'
# default if unknown/empty
return 'Catalog, Search'
# --- load file ---
raw = open(SRC, 'rb').read()
text = strip_bom_bytes(raw).decode('utf-8', errors='replace').replace('\r\n','\n').replace('\r','\n')
rows = list(csv.reader(text.split('\n')))
if not rows:
print("Empty CSV", file=sys.stderr); sys.exit(2)
# header clean + dedupe (keep first)
orig_header = [norm_space(h or '') for h in rows[0]]
seen = set(); header = []; idx_map = []
for i,h in enumerate(orig_header):
k = h.lower()
if not k or k in seen: continue
seen.add(k); header.append(h); idx_map.append(i)
# ensure required columns
lower = [h.lower() for h in header]
if 'product_online' not in lower:
header.append('product_online'); lower.append('product_online')
if 'url_key' not in lower:
header.append('url_key'); lower.append('url_key')
# case-insensitive indices
idx = {h.lower(): i for i,h in enumerate(header)}
# prepare output
out = [header]
seen_urls = set()
header_lower_fingerprint = lower[:]
for r in rows[1:]:
if not any(r): # skip blank lines
continue
# project through first-dedupe header map
row1 = [r[i] if i < len(r) else '' for i in idx_map]
# mid-file header guard
low_row = [norm_space(x or '').lower() for x in row1]
if looks_like_header(low_row, header_lower_fingerprint):
continue
# expand/trim to header width
if len(row1) < len(header): row1 += [''] * (len(header) - len(row1))
elif len(row1) > len(header): row1 = row1[:len(header)]
# visibility normalization (if present)
if 'visibility' in idx:
vpos = idx['visibility']
row1[vpos] = normalize_visibility(row1[vpos])
# product_online -> default 1
row1[idx['product_online']] = '1' if not row1[idx['product_online']].strip() else row1[idx['product_online']].strip()
# url_key -> generate if missing + unique (prefers name, falls back to sku)
upos = idx['url_key']
url = (row1[upos] or '').strip()
sku = (row1[idx['sku']]].strip() if 'sku' in idx else '')
name = (row1[idx['name']]].strip() if 'name' in idx else '')
if not url:
base = slugify(name or sku)
cand = base + (('-' + sku.lower()) if sku else '')
else:
cand = slugify(url)
u = cand; n = 1
while u in seen_urls:
n += 1; u = f"{cand}-{n}"
seen_urls.add(u)
row1[upos] = u
out.append(row1)
with open(DST, 'w', newline='', encoding='utf-8') as g:
csv.writer(g).writerows(out)
print(DST)
PY
# Run it
python3 /var/www/shop.einkaufsring.com/sanitize_keep_visibility.py
# Import the produced CSV
sudo -u www-data php bin/magento feed:import:run /tmp/Artikelexport_sanitized_with_visibility.csv