import glob import os import re import sys THEME_REPO_URL = "https://git.falkenthal.me/falkenthal/pretix-falkenthal-theme" PLATFORM_IMPRINT_URL = "https://tickets.falkenthal.me/impressum" PLATFORM_PRIVACY_URL = "https://tickets.falkenthal.me/datenschutz" ASSET_VERSION = "1.8.5" control_files = [ '/pretix/src/pretix/control/templates/pretixcontrol/base.html', '/pretix/src/pretix/control/templates/pretixcontrol/auth/base.html', ] platform_footer_html = ( '
' '
' 'Technischer Plattformbetrieb: FALKENTHAL' '·' f'Plattform-Impressum' '·' f'Plattform-Datenschutz' '·' f'Theme & Quellcode (AGPLv3)' '
' '
' '' ) # 1. Control & Auth Templates patchen (Plattform-Backend: immer Falkenthal-Branding) for p in control_files: if not os.path.exists(p): print(f"ERROR: Expected template not found: {p}", file=sys.stderr) sys.exit(1) with open(p, 'r', encoding='utf-8') as f: c = f.read() # Standard-Pretix-Favicon im Backend durch Falkenthal-Favicon ersetzen c = re.sub(r']*rel=[\"\'](shortcut )?icon[\"\'][^>]*>', '', c, flags=re.I) # Eindeutige Body-Klasse für Auth/Login-Seiten setzen (verhindert CSS-Leaks im normalen Backend) if p.endswith('auth/base.html') and 'falkenthal-auth-body' not in c: if ']*)class=["\']([^"\']*)["\']', r'' not in c: print(f"ERROR: tag not found in {p}", file=sys.stderr) sys.exit(1) c = c.replace( '', f'' f'' f'' ) if 'falkenthal-platform-bar' not in c: if '' not in c: print(f"ERROR: tag not found in {p}", file=sys.stderr) sys.exit(1) c = c.replace('', platform_footer_html) with open(p, 'w', encoding='utf-8') as f: f.write(c) # 2. Presale Template patchen (Ticketshop: Veranstalter-Favicons respektieren, Fallback = Falkenthal) presale_file = '/pretix/src/pretix/presale/templates/pretixpresale/base.html' if not os.path.exists(presale_file): print(f"ERROR: Expected template not found: {presale_file}", file=sys.stderr) sys.exit(1) with open(presale_file, 'r', encoding='utf-8') as f: c = f.read() # Nur die statischen Standard-Pretix-Favicon-Fallbacks ersetzen, damit individuelle # Veranstalter-/Event-Favicons ({% if request.event.settings.favicon %}) erhalten bleiben! c = re.sub( r']*rel=[\"\'](shortcut )?icon[\"\'][^>]*static\s+[\"\']pretixbase/img/favicon[^\"\']*[\"\'][^>]*>', '', c, flags=re.I, ) # Falls in einem {% else %}-Zweig der Standard-Favicon-Block nun leer wäre oder direkt vor : if '/static/whitelabel/presale.css' not in c: if '' not in c: print(f"ERROR: tag not found in {presale_file}", file=sys.stderr) sys.exit(1) presale_head_inject = ( '{% if not request.event or not request.event.settings.favicon %}' f'' f'' '{% endif %}' f'' ) c = c.replace('', presale_head_inject) if 'falkenthal-platform-bar' not in c: if '' not in c: print(f"ERROR: tag not found in {presale_file}", file=sys.stderr) sys.exit(1) c = c.replace('', platform_footer_html) with open(presale_file, 'w', encoding='utf-8') as f: f.write(c) # 3. Globale Fehlerseiten (400, 403, 404, 500, 503) mit Plattform-Rechtstexten ausstatten error_templates_dir = '/pretix/src/pretix/base/templates' for err_name in ['400.html', '403.html', '403_csrf.html', '404.html', '500.html', '503.html']: err_path = os.path.join(error_templates_dir, err_name) if os.path.exists(err_path): with open(err_path, 'r', encoding='utf-8') as f: ec = f.read() if 'falkenthal-platform-bar' not in ec and '' in ec: error_footer = ( '
' 'Technischer Plattformbetrieb: FALKENTHAL' ' · ' f'Plattform-Impressum' ' · ' f'Plattform-Datenschutz' ' · ' f'Theme & Quellcode (AGPLv3)' '
' ) ec = ec.replace('', error_footer) with open(err_path, 'w', encoding='utf-8') as f: f.write(ec) # 4. Eingebettetes Pretix-Widget (/widget/v1.*.js & widget.py): # Einheitliche Plattform-Pflichtangaben garantiert in jedem (auch auf Jimdo & bei leeren Events)! widget_platform_bar_js = r""" ;(function(){ if (typeof document === 'undefined' || window.__falkenthalWidgetPatched) return; window.__falkenthalWidgetPatched = true; var BAR_HTML = 'Technischer Plattformbetrieb: FALKENTHAL' + ' · Plattform-Impressum' + ' · Plattform-Datenschutz' + ' · Theme & Quellcode (AGPLv3)'; function injectWidgetBar() { var containers = document.querySelectorAll('.pretix-widget-attribution, .pretix-widget'); for (var i = 0; i < containers.length; i++) { var el = containers[i]; var widgetRoot = el.classList.contains('pretix-widget') ? el : el.closest('.pretix-widget'); var checkTarget = widgetRoot || el; if (checkTarget.querySelector('.pretix-widget-falkenthal-bar')) continue; var target = checkTarget.querySelector('.pretix-widget-attribution') || checkTarget; var bar = document.createElement('div'); bar.className = 'pretix-widget-falkenthal-bar'; bar.style.cssText = 'margin-top:8px;padding-top:8px;border-top:1px solid rgba(128,128,128,0.22);font-size:11px;line-height:1.5;text-align:center;opacity:0.85;clear:both;'; bar.innerHTML = BAR_HTML; target.appendChild(bar); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', injectWidgetBar); } else { injectWidgetBar(); } if (typeof MutationObserver !== 'undefined') { var obs = new MutationObserver(function() { injectWidgetBar(); }); var startObs = function() { if (document.body) obs.observe(document.body, { childList: true, subtree: true }); }; if (document.body) startObs(); else document.addEventListener('DOMContentLoaded', startObs); } setInterval(injectWidgetBar, 1000); })(); """ # 4a. Alle statischen Widget-JS-Dateien im gesamten Pretix-Baum finden (z.B. ui/widget.js, widget/*.js, static.dist) widget_js_candidates = ( glob.glob('/pretix/src/pretix/presale/static/**/*widget*.js', recursive=True) + glob.glob('/pretix/src/pretix/static.dist/**/*widget*.js', recursive=True) ) patched_js_count = 0 for wjs in set(widget_js_candidates): if os.path.isfile(wjs) and not wjs.endswith('.min.js.map'): with open(wjs, 'r', encoding='utf-8') as f: wc = f.read() if '__falkenthalWidgetPatched' not in wc: with open(wjs, 'a', encoding='utf-8') as f: f.write('\n' + widget_platform_bar_js) patched_js_count += 1 # 4b. Zusätzlich direkt die Django-View /pretix/src/pretix/presale/views/widget.py patchen, # da Pretix /widget/v1.de.js dynamisch über diese View generiert, im Dateisystem speichert und ausliefert! widget_view_py = '/pretix/src/pretix/presale/views/widget.py' if os.path.exists(widget_view_py): with open(widget_view_py, 'r', encoding='utf-8') as f: wpy = f.read() if '__falkenthalWidgetPatched' not in wpy: wpy_patch = ( f"\n\n# --- FALKENTHAL PLATFORM LEGAL BAR INJECTION ---\n" f"_FALKENTHAL_WIDGET_JS = {repr(widget_platform_bar_js)}\n" f"if 'generate_widget_js' in globals():\n" f" _orig_generate_widget_js = generate_widget_js\n" f" def generate_widget_js(lang):\n" f" code = _orig_generate_widget_js(lang)\n" f" if '__falkenthalWidgetPatched' not in code:\n" f" code = code + '\\n' + _FALKENTHAL_WIDGET_JS\n" f" return code\n" f"if 'widget_js' in globals():\n" f" _orig_widget_js = widget_js\n" f" def widget_js(request, *args, **kwargs):\n" f" resp = _orig_widget_js(request, *args, **kwargs)\n" f" if hasattr(resp, 'content') and b'__falkenthalWidgetPatched' not in resp.content:\n" f" resp.content = resp.content + _FALKENTHAL_WIDGET_JS.encode('utf-8')\n" f" return resp\n" ) with open(widget_view_py, 'a', encoding='utf-8') as f: f.write(wpy_patch) print(f"Pretix templates and widget assets ({patched_js_count} JS files + widget.py) patched and verified successfully (v{ASSET_VERSION}).")