110 lines
4.3 KiB
PHP
110 lines
4.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Legal;
|
|
|
|
use App\Console\Commands\PruneDormantAccounts;
|
|
use App\Console\Commands\PruneUnverifiedAccounts;
|
|
use App\Support\CompanyProfile;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\View;
|
|
use TCPDF;
|
|
|
|
/**
|
|
* The processing agreement and its annex, as PDFs.
|
|
*
|
|
* TCPDF, because the invoices already render through it — a second PDF engine
|
|
* for two documents a year would be a dependency nobody maintains. Its HTML
|
|
* support is not a browser's, so the two Blade views are deliberately plain:
|
|
* headings, paragraphs, lists, one table.
|
|
*
|
|
* ## The documents are generated, not uploaded
|
|
*
|
|
* They name the company from CompanyProfile and take their deadlines from the
|
|
* commands that enforce them, so the agreement cannot drift from the invoices or
|
|
* from what the software actually does. An operator can still upload a lawyer's
|
|
* revision as a new version — the console does not care where a file came from.
|
|
*/
|
|
final class DpaRenderer
|
|
{
|
|
/**
|
|
* The sub-processors this installation actually talks to.
|
|
*
|
|
* Here rather than in the template because it is a fact about the system,
|
|
* not wording: hosting, payment and mail are the three places customer-
|
|
* related data leaves this application, and a list that quietly omits one is
|
|
* the single most common defect in a processing agreement.
|
|
*
|
|
* @return array<int, array<string, string>>
|
|
*/
|
|
public static function subprocessors(): array
|
|
{
|
|
return [
|
|
[
|
|
'name' => 'Hetzner Online GmbH',
|
|
'service' => 'Rechenzentrum, Server und Netzanbindung der Kundeninstanzen',
|
|
'location' => 'Falkenstein und Nürnberg, Deutschland; Helsinki, Finnland',
|
|
],
|
|
[
|
|
'name' => 'Stripe Payments Europe, Ltd.',
|
|
'service' => 'Zahlungsabwicklung (Vertrags- und Zahlungsdaten des Kunden, keine Instanzinhalte)',
|
|
'location' => 'Irland (EU)',
|
|
],
|
|
[
|
|
'name' => 'thinkidoo e.U.',
|
|
'service' => 'Mailserver für den Versand und Empfang der Betriebs- und Support-E-Mails',
|
|
'location' => 'Österreich',
|
|
],
|
|
];
|
|
}
|
|
|
|
/** The agreement itself. */
|
|
public function agreement(string $version, ?Carbon $issuedOn = null): string
|
|
{
|
|
return $this->render('legal.dpa.agreement', $version, $issuedOn, [
|
|
'subprocessors' => self::subprocessors(),
|
|
]);
|
|
}
|
|
|
|
/** Annex 1: the technical and organisational measures. */
|
|
public function measures(string $version, ?Carbon $issuedOn = null): string
|
|
{
|
|
return $this->render('legal.dpa.measures', $version, $issuedOn, [
|
|
// From the commands that enforce them, so the document cannot
|
|
// promise one deadline while the sweep runs another.
|
|
'unverifiedDays' => PruneUnverifiedAccounts::AFTER_DAYS,
|
|
'warnDays' => PruneDormantAccounts::WARN_DAYS_BEFORE,
|
|
]);
|
|
}
|
|
|
|
/** @param array<string, mixed> $data */
|
|
private function render(string $view, string $version, ?Carbon $issuedOn, array $data): string
|
|
{
|
|
$issuedOn ??= now();
|
|
|
|
$html = View::make($view, array_merge($data, [
|
|
'version' => $version,
|
|
// R19: written on the wall clock, because a date on a contract is
|
|
// read by a person and not by a server.
|
|
'issuedOn' => $issuedOn->local()->isoFormat('LL'),
|
|
]))->render();
|
|
|
|
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8');
|
|
$pdf->SetCreator('CluPilot');
|
|
$pdf->SetAuthor((string) (CompanyProfile::get('name') ?? 'CluPilot'));
|
|
$pdf->SetTitle(__('dpa.title').' '.$version);
|
|
// No default header or footer bar: this is a contract, not a report, and
|
|
// TCPDF's default header prints a black line and a title nobody set.
|
|
$pdf->setPrintHeader(false);
|
|
$pdf->setPrintFooter(true);
|
|
$pdf->SetMargins(20, 18, 20);
|
|
$pdf->SetAutoPageBreak(true, 20);
|
|
// DejaVu, because the text is German: TCPDF's core fonts have no ß in
|
|
// the encoding this uses and would print it as a blank.
|
|
$pdf->SetFont('dejavusans', '', 9.5);
|
|
$pdf->AddPage();
|
|
$pdf->writeHTML($html, true, false, true, false, '');
|
|
|
|
return (string) $pdf->Output('', 'S');
|
|
}
|
|
}
|