Sanitize survey frontend and PDF output
This commit is contained in:
@@ -2,7 +2,10 @@
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ documentTitle|striptags|trim }}</title>
|
||||
<style>
|
||||
{{ fontFaceCss|raw }}
|
||||
|
||||
@page {
|
||||
margin: 20mm 14mm 18mm;
|
||||
}
|
||||
@@ -10,7 +13,7 @@
|
||||
body {
|
||||
margin: 0;
|
||||
color: #222222;
|
||||
font-family: DejaVu Sans, sans-serif;
|
||||
font-family: "PT Sans Narrow", sans-serif;
|
||||
font-size: 11px;
|
||||
line-height: 1.45;
|
||||
background: #ffffff;
|
||||
@@ -33,6 +36,7 @@
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 22px;
|
||||
font-family: "Blogger Sans", sans-serif;
|
||||
color: #0f4f88;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@@ -64,6 +68,7 @@
|
||||
|
||||
.question-card h2 {
|
||||
font-size: 16px;
|
||||
font-family: "Blogger Sans", sans-serif;
|
||||
color: #0f4f88;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
@@ -174,6 +179,7 @@
|
||||
.metric-value {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
font-family: "Blogger Sans", sans-serif;
|
||||
color: #0f4f88;
|
||||
}
|
||||
|
||||
@@ -196,8 +202,8 @@
|
||||
</head>
|
||||
<body>
|
||||
<header class="page-header">
|
||||
<h1>{{ survey.title }}</h1>
|
||||
<p>{{ survey.description ?: 'Keine Beschreibung hinterlegt.' }}</p>
|
||||
<h1>{{ survey.title|striptags|trim }}</h1>
|
||||
<p>{{ (survey.description|striptags|trim) ?: 'Keine Beschreibung hinterlegt.' }}</p>
|
||||
|
||||
<div class="meta-row">
|
||||
<span class="meta-pill">{{ completedSubmissionCount }} abgeschlossene Teilnahmen</span>
|
||||
@@ -208,10 +214,10 @@
|
||||
|
||||
{% for question in questionResults %}
|
||||
<section class="question-card">
|
||||
<h2>{{ question.question }}</h2>
|
||||
<h2>{{ question.question|striptags|trim }}</h2>
|
||||
|
||||
{% if question.description %}
|
||||
<p class="question-description">{{ question.description }}</p>
|
||||
{% if question.description|striptags|trim %}
|
||||
<p class="question-description">{{ question.description|striptags|trim }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="meta-row">
|
||||
@@ -221,7 +227,7 @@
|
||||
|
||||
{% if question.pdfChartDataUri %}
|
||||
<div class="chart-box chart-{{ question.pdfChartType ?: 'default' }}">
|
||||
<img src="{{ question.pdfChartDataUri }}" alt="Diagramm zu {{ question.question }}">
|
||||
<img src="{{ question.pdfChartDataUri }}" alt="Diagramm zu {{ question.question|striptags|trim }}">
|
||||
{% if question.type == 'range' and question.pdfChartLegend %}
|
||||
<div class="chart-hint">Die Punkte sind farblich den Werten in der Tabelle zugeordnet.</div>
|
||||
{% endif %}
|
||||
@@ -242,7 +248,7 @@
|
||||
{% for option in question.pdfChartLegend %}
|
||||
<tr>
|
||||
<td><span class="swatch" style="background: {{ option.fillColor }}; border-color: {{ option.borderColor }};"></span></td>
|
||||
<td>{{ option.label }}</td>
|
||||
<td>{{ option.label|striptags|trim }}</td>
|
||||
<td class="numeric">{{ option.count }}</td>
|
||||
<td class="numeric">{{ option.percentage }}%</td>
|
||||
</tr>
|
||||
@@ -282,7 +288,7 @@
|
||||
{% for option in question.pdfChartLegend %}
|
||||
<tr>
|
||||
<td><span class="swatch" style="background: {{ option.fillColor }}; border-color: {{ option.borderColor }};"></span></td>
|
||||
<td>{{ option.label }}</td>
|
||||
<td>{{ option.label|striptags|trim }}</td>
|
||||
<td class="numeric">{{ option.count }}</td>
|
||||
<td class="numeric">{{ option.percentage }}%</td>
|
||||
</tr>
|
||||
@@ -308,7 +314,7 @@
|
||||
{% for option in question.pdfChartLegend %}
|
||||
<tr>
|
||||
<td><span class="swatch" style="background: {{ option.fillColor }}; border-color: {{ option.borderColor }};"></span></td>
|
||||
<td>{{ option.label }}</td>
|
||||
<td>{{ option.label|striptags|trim }}</td>
|
||||
<td class="numeric">{{ option.count }}</td>
|
||||
<td class="numeric">{{ option.percentage }}%</td>
|
||||
</tr>
|
||||
@@ -319,7 +325,7 @@
|
||||
|
||||
<ol class="response-list">
|
||||
{% for response in question.responses %}
|
||||
<li>{{ response }}</li>
|
||||
<li>{{ response|striptags|trim }}</li>
|
||||
{% else %}
|
||||
<li>Noch keine Freitextantworten vorhanden.</li>
|
||||
{% endfor %}
|
||||
|
||||
@@ -197,9 +197,46 @@ final class SurveyResultsPdfService
|
||||
'completedSubmissionCount' => $completedSubmissionCount,
|
||||
'questionResults' => $questionResults,
|
||||
'exportedAt' => (new \DateTimeImmutable())->format('d.m.Y H:i'),
|
||||
'documentTitle' => $this->buildDocumentTitle((string) $survey->title),
|
||||
'fontFaceCss' => $this->buildEmbeddedFontFaceCss(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function buildDocumentTitle(string $surveyTitle): string
|
||||
{
|
||||
$title = trim(strip_tags($surveyTitle));
|
||||
|
||||
return '' !== $title ? $title.' Ergebnisse' : 'Umfrage Ergebnisse';
|
||||
}
|
||||
|
||||
private function buildEmbeddedFontFaceCss(): string
|
||||
{
|
||||
$fontDefinitions = [
|
||||
['family' => 'PT Sans Narrow', 'weight' => 400, 'path' => __DIR__.'/../../public/fonts/PTSansNarrow-Regular.woff2'],
|
||||
['family' => 'PT Sans Narrow', 'weight' => 700, 'path' => __DIR__.'/../../public/fonts/PTSansNarrow-Bold.woff2'],
|
||||
['family' => 'Blogger Sans', 'weight' => 500, 'path' => __DIR__.'/../../public/fonts/Blogger Sans-Medium.woff2'],
|
||||
];
|
||||
|
||||
$rules = [];
|
||||
|
||||
foreach ($fontDefinitions as $fontDefinition) {
|
||||
$fontContent = @file_get_contents($fontDefinition['path']);
|
||||
|
||||
if (false === $fontContent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rules[] = sprintf(
|
||||
'@font-face { font-family: "%s"; src: url("data:font/woff2;base64,%s") format("woff2"); font-weight: %d; font-style: normal; font-display: swap; }',
|
||||
$fontDefinition['family'],
|
||||
base64_encode($fontContent),
|
||||
$fontDefinition['weight'],
|
||||
);
|
||||
}
|
||||
|
||||
return implode("\n", $rules);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $questionResult
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user