organic: Add extended API

This commit is contained in:
2026-09-01 18:02:57 +02:00
parent 028c16be3d
commit 4572323a14
13 changed files with 978 additions and 266 deletions
+229
View File
@@ -0,0 +1,229 @@
<?php
function get_address($array, $from, $to): array {
$address = [];
$postalCode = [];
$city = [];
for ($i = $to; $i >= $from; $i--) {
$el = $array[$i];
if (sizeof($postalCode) > 0) {
if (sizeof($address) === 0) $el = rtrim($el, ", \n\r\t\v\0");
if (strlen($el) === 0) continue;
array_unshift($address, $el);
} else if (preg_match("/^[A-Z0-9.\-]{3,},?$/", $el)) {
array_unshift($postalCode, trim($el, ", \n\r\t\v\0"));
} else {
array_unshift($city, $el);
}
}
return [implode(' ', $address), implode(' ', $postalCode), implode(' ', $city)];
}
function parse_pdf(string $filename, bool $loadUrl = false): array | null {
if (!file_exists($filename))
return null;
if (exec("pdftotext -raw " . escapeshellarg($filename) . " -", $text) === false)
return null;
$text = implode("\n", $text);
exec("pdfsig " . escapeshellarg($filename), $sig);
$sig = implode("\n", $sig);
if ($loadUrl) {
$prefix = "/tmp/pdfimages-" . uniqid();
exec("zbarimg -q --raw $(pdfimages -print-filenames " . escapeshellarg($filename) . " $prefix) | uniq; rm -rf $prefix-*.*", $qrCodes);
} else {
$qrCodes = null;
}
$r = preg_match('@([a-z]{2}) (https://webgate\.ec\.europa\.eu/tracesnt/directory/publication/organic-operator/(.*?)\.pdf) (\d+) / (\d+)@', $text, $matches);
if ($r === 1) {
// TRACES certificate
$data = [];
$parts = preg_split('@\n(I+\.\d+) ([^\n]*)@', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
$status = str_replace("\n", '', $parts[0]);
for ($i = 3; $i < sizeof($parts); $i += 3) {
$data[$parts[$i - 2]] = trim($parts[$i]);
}
$lang = $matches[1];
$splitAddr = [
'de' => 'Adresse',
'en' => 'Address',
][$lang];
$splitCountry = [
'de' => 'Land',
'en' => 'Country',
][$lang];
$statusMap = [
'de' => [
'AUSGESTELLT' => 'issued',
],
'en' => [
'ISSUED' => 'issued',
]
][$lang];
$activityMap = [
'de' => [
'Aufbereitung' => 'preparation',
'Ausfuhr' => 'export',
'Einfuhr' => 'import',
'Lagerung' => 'storing',
'Produktion' => 'production',
'Vertrieb' => 'distribution',
'Vertrieb/Inverkehrbringen' => 'distribution_placing_on_the_market',
],
'en' => [
'Distribution' => 'distribution',
'Distribution/Placing on the market' => 'distribution_placing_on_the_market',
'Export' => 'export',
'Import' => 'import',
'Preparation' => 'preparation',
'Production' => 'production',
'Storing' => 'storing',
],
][$lang];
$certUrl = $matches[2];
$certId = $matches[3];
$authorityId = explode('.', $certId)[0];
$operatorId = explode('.', $certId)[1];
$operator = preg_split('@\s+@', trim($data['I.3']));
$p1 = array_search($splitAddr, $operator);
$p2 = array_search($splitCountry, $operator);
$operatorName = trim(implode(' ', array_filter($operator, fn($k,$i) => $i > 0 && $i < $p1, ARRAY_FILTER_USE_BOTH)), ', ');
[$opAddr, $opPostal, $opCity] = get_address($operator, $p1 + 1, $p2 - 1);
$authority = preg_split('@\s+@', trim($data['I.4']));
$until = array_search("($authorityId)", $authority);
$p1 = array_search($splitAddr, $authority);
$p2 = array_search($splitCountry, $authority);
$authorityName = implode(' ', array_filter($authority, fn($k,$i) => $i > 0 && $i < $p1 - 1 && ($i !== $p1 - 2 || !str_starts_with($k, '(')), ARRAY_FILTER_USE_BOTH));
[$aAddr, $aPostal, $aCity] = get_address($authority, $p1 + 1, $p2 - 1);
$activities = [];
foreach (explode("\n", $data['I.5']) as $a) {
$activities[] = $activityMap[trim($a, '• ')];
}
preg_match_all('/\([a-g]\)/', $data['I.6'], $matches, PREG_SET_ORDER);
$products = [];
foreach ($matches as $m) {
$products[] = $m[0];
}
preg_match_all('@\d+/\d+/\d+@', $data['I.8'], $matches, PREG_SET_ORDER);
$valid1 = implode('-', array_reverse(explode('/', $matches[0][0])));
$valid2 = implode('-', array_reverse(explode('/', $matches[1][0])));
$sigs = [];
foreach (array_slice(explode("\nSignature #", $sig), 1) as $s) {
$sData = [];
$sData2 = [];
preg_match_all('/\n {2}- (([^:\n]*): )?([^\n]*)/', $s, $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
if (strlen($m[2]) === 0) {
$sData2[] = $m[3];
} else {
$sData[$m[2]] = $m[3];
}
}
$sigs[] = [
'signerCommonName' => $sData['Signer Certificate Common Name'],
'valid' => $sData['Signature Validation'] === 'Signature is Valid.',
'trusted' => $sData['Certificate Validation'] === 'Certificate is Trusted.',
'totalDocument' => in_array('Total document signed', $sData2),
'timestamp' => gmdate('Y-m-d\TH:i:s\Z', strtotime($sData['Signing Time'])),
'type' => $sData['Signature Type'],
'hashAlgorithm' => $sData['Signing Hash Algorithm'],
'signerDistinguishedName' => $sData['Signer full Distinguished Name'],
'fieldName' => $sData['Signature Field Name'],
];
}
return [
'type' => 'traces',
'lang' => $lang,
'id' => $certId,
'status' => $statusMap[$status],
'operator' => [
'id' => $operatorId,
'groupOfOperators' => !str_starts_with($data['I.2'], '☑'),
'name' => $operatorName,
'address' => $opAddr,
'postalCode' => $opPostal,
'city' => $opCity,
'countryCode' => $operator[sizeof($operator) - 1],
],
'authority' => [
'id' => $authorityId,
'name' => $authorityName,
'address' => $aAddr,
'postalCode' => $aPostal,
'city' => $aCity,
'countryCode' => $authority[sizeof($authority) - 1],
],
'activities' => $activities,
'productCategories' => $products,
'validFrom' => $valid1,
'validTo' => $valid2,
'url' => $certUrl,
'digitalSignatures' => $sigs,
'qrCodes' => $qrCodes,
];
}
$isLacon = str_contains($text, "\nLACON GmbH\n") && (str_contains($text, "\nAnlage zum Zertifikat\n"));
if (preg_match('/AT-BIO-[0-9]{3}/', $text, $matches) === 1 || $isLacon) {
$authorityId = $matches[0] ?? ($isLacon ? 'AT-BIO-402' : null);
$certId = null;
$certNr = null;
if (preg_match("/$authorityId\.040-[0-9]{7}\.[0-9]{4}\.[0-9]{3}/", $text, $matches) === 1) {
$certId = $matches[0];
}
if (preg_match_all("/\b[0-9]+([._-])[0-9]+\g{-1}[0-9]+\b/", $text, $matches, PREG_SET_ORDER) !== false) {
foreach ($matches as $m) {
if (strlen($m[0]) > 10 && !str_ends_with($certId, $m[0]))
$certNr = $m[0];
}
}
if ($certId === null && $qrCodes !== null && sizeof($qrCodes) > 0 && str_starts_with($qrCodes[0], 'www.easy-cert.com/')) {
$res = exec("curl -L " . escapeshellarg($qrCodes[0]) . " | grep -A 100 '<table' | grep -B 100 '</table>' | sed 's/<[^>]*>//g;s/\\s\\+/ /g;s/^ //g;s/ $//g;s/\\n//m' | grep 'Certificate number' -A 1");
if ($res !== false && preg_match("/$authorityId\.040-[0-9]{7}\.[0-9]{4}\.[0-9]{3}/", $res, $matches) === 1) {
$certId = $matches[0];
}
}
$operator = ['lfbisNr' => null];
if ($authorityId === 'AT-BIO-301') {
// TODO
} else if ($authorityId === 'AT-BIO-302') {
if (preg_match("/^lfbis[^0-9]*([0-9]{6,7})$/im", $text, $matches) === 1)
$operator['lfbisNr'] = str_pad($matches[1], 7, '0', STR_PAD_LEFT);
} else if ($authorityId === 'AT-BIO-401') {
// TODO
} else if ($authorityId === 'AT-BIO-402') {
if (preg_match("/^Betriebsnummer ([0-9]{6,7})$/im", $text, $matches) === 1)
$operator['lfbisNr'] = str_pad($matches[1], 7, '0', STR_PAD_LEFT);
} else if ($authorityId === 'AT-BIO-902') {
if (preg_match("/ lfbis:([0-9]{6,7})$/im", $text, $matches) === 1)
$operator['lfbisNr'] = str_pad($matches[1], 7, '0', STR_PAD_LEFT);
} else if ($authorityId === 'AT-BIO-903') {
// TODO
}
return [
'type' => $authorityId,
'lang' => 'de',
'id' => $certId,
'nr' => $certNr,
'operator' => $operator,
'authority' => ['id' => $authorityId],
'qrCodes' => $qrCodes,
];
}
return ['type' => 'unknown'];
}
View File
+44
View File
@@ -0,0 +1,44 @@
<?php
require '../.php/pdf.php';
header('Access-Control-Allow-Origin: *');
header('Content-Type: application/json; charset=UTF-8');
if ($_SERVER['REQUEST_METHOD'] !== 'GET' && $_SERVER['REQUEST_METHOD'] !== 'HEAD') {
header('Status: 405');
header('Content-Length: 56');
header('Allow: GET, HEAD');
echo "{\"error\":\"method not allowed\",\"allow\":[\"GET\",\"HEAD\"]}\n";
exit;
}
function jenc($data): string {
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
$info = substr($_SERVER['PATH_INFO'], 1);
if (strlen($info) !== 31) {
header('Status: 404');
header('Content-Length: 22');
echo "{\"error\":\"not found\"}\n";
exit;
}
$cert = parse_pdf("$info.pdf");
$appendix = parse_pdf("$info.appendix.pdf");
if ($cert === null && $appendix === null) {
header('Status: 404');
header('Content-Length: 22');
echo "{\"error\":\"not found\"}\n";
exit;
}
if ($cert !== null) $cert['pdfUrl'] = "https://elwig.at/organic/certificates/$info.pdf";
if ($appendix !== null) $appendix['pdfUrl'] = "https://elwig.at/organic/certificates/$info.appendix.pdf";
echo jenc([
'traces' => $cert,
'appendix' => $appendix,
]);
+5 -5
View File
@@ -121,7 +121,8 @@ if ($country === null) {
$data = [];
$url = "https://www.bioc.info/search/producerSearchQuery?search[name]=" . urlencode($name) . "&search[citycode]=" . urlencode($postalCode) . "&search[operatorId]=" . urlencode($idNr) . "&search[country]=$country";
$origin = "https://www.bioc.info/search/producersearchresult?producerSearch%5Bcountry%5D={$country}&producerSearch%5Bcitycode%5D=" . urlencode($postalCode) . "&producerSearch%5Bname%5D=" . urlencode($name) . "&producerSearch%5BoperatorId%5D=" . urlencode($idNr);
$url = "https://www.bioc.info/search/producerSearchQuery?search[country]={$country}&search[citycode]=" . urlencode($postalCode) . "&search[name]=" . urlencode($name) . "&search[operatorId]=" . urlencode($idNr);
$m = curl_multi_init();
$requests = [];
foreach ($source_groups as $group) {
@@ -134,6 +135,7 @@ foreach ($source_groups as $group) {
$running = 1;
do {
curl_multi_exec($m, $running);
if ($running) usleep(250_000);
} while ($running);
foreach ($requests as $r) {
curl_multi_remove_handle($m, $r);
@@ -141,7 +143,7 @@ foreach ($requests as $r) {
curl_multi_close($m);
foreach ($requests as $s) {
if (($json = curl_multi_getcontent($s)) === false) {
if (($json = curl_multi_getcontent($s)) === null) {
header('Status: 500');
header('Content-Length: 34');
echo "{\"error\":\"internal server error\"}\n";
@@ -153,10 +155,8 @@ foreach ($requests as $s) {
}
}
header('Content-Type: application/json; charset=UTF-8');
$first = true;
echo "{\"data\":[\n";
echo "{\"originUrl\":\"$origin\",\"data\":[\n";
foreach ($data as $id => $row) {
if (!$first) echo ",\n";
$row = explode("</", $row);
+1
View File
@@ -18,6 +18,7 @@ def main() -> None:
args = parser.parse_args()
s = requests.Session()
s.verify = False
while True:
try:
r = s.get(f'{URL}?menu_sid=5002')
+6
View File
@@ -33,6 +33,8 @@ def main() -> None:
query = {'PartnerCertSearchForm:pcs_' + q.split('=', 1)[0]: urllib.parse.unquote(q.split('=', 1)[-1]) for q in args.query.split('&')}
s = requests.Session()
s.verify = False
try:
while True:
try:
r = s.get(f'{URL}?menu_sid=5002')
@@ -56,6 +58,10 @@ def main() -> None:
collapsed_rows = [[tuple(remove_tags((ATTACHMENT_RE.search(m[1]) or m)[1]) for m in TD_RE.finditer(row[1]))
for row in ROW_RE.finditer(tbl[0])]
for tbl in COLLAPSED_ROW_RE.finditer(result_table)]
except:
print('[]')
return
print('[')
first = True
for row, tbl in zip(uncollapsed_rows, collapsed_rows):
+141
View File
@@ -0,0 +1,141 @@
<?php
header('Access-Control-Allow-Origin: *');
header('Content-Type: application/json; charset=UTF-8');
$base = 'https://' . $_SERVER['SERVER_NAME'] . explode('?', $_SERVER['REQUEST_URI'])[0];
if ($_SERVER['REQUEST_METHOD'] !== 'GET' && $_SERVER['REQUEST_METHOD'] !== 'HEAD') {
header('Status: 405');
header('Content-Length: 56');
header('Allow: GET, HEAD');
echo "{\"error\":\"method not allowed\",\"allow\":[\"GET\",\"HEAD\"]}\n";
exit;
}
function jenc($data): string {
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
$info = $_SERVER['PATH_INFO'];
if ($info !== '') {
header('Status: 404');
header('Content-Length: 22');
echo "{\"error\":\"not found\"}\n";
exit;
}
$country = $_GET['country'] ?? null;
$postalCode = $_GET['postalCode'] ?? null;
$m = curl_multi_init();
$requests = [];
foreach (['bioc', 'bioqs', 'easy-cert', 'lkv'] as $api) {
$s = curl_init("https://elwig.at/organic/external/$api/operators?country=$country&postalCode=$postalCode");
$requests[] = $s;
curl_setopt($s, CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($m, $s);
}
$running = 1;
do {
curl_multi_exec($m, $running);
if ($running) usleep(250_000);
} while ($running);
foreach ($requests as $r) {
curl_multi_remove_handle($m, $r);
}
curl_multi_close($m);
$data = [];
foreach ($requests as $s) {
if (($json = curl_multi_getcontent($s)) === null) {
header('Status: 500');
header('Content-Length: 34');
echo "{\"error\":\"internal server error\"}\n";
exit;
}
$data[] = json_decode($json, true)['data'];
}
$operators = [];
foreach ($data[2] as $op) $operators[] = ['easyCert' => $op];
foreach ($data[0] as $op) {
if (($m = array_find($operators, fn($o) => isset($o['easyCert']) && $o['easyCert']['id'] === $op['id'])) !== null) {
$m['bioc'] = $op;
} else if (isset($op['details']) && ($m = array_find($operators, fn($o) => isset($o['easyCert']) && $o['easyCert']['idNr'] === $op['details']['idNr'])) !== null) {
$m['bioc'] = $op;
} else if (isset($op['details']) && ($m = array_find($operators, fn($o) => isset($o['bioc']['details']) && isset($op['details']) && $o['bioc']['name'] === $op['name'] && $o['bioc']['details']['address'] === $op['details']['address'])) !== null) {
$m['bioc'] = $op;
} else {
$operators[] = ['bioc' => $op];
}
}
foreach ($data[1] as $op) {
if (false) {
} else {
$operators[] = ['bioqs' => $op];
}
}
foreach ($data[3] as $op) $operators[] = ['lkv' => $op];
// for (const op of easyCert.data) operators.push({easyCert: op});
// for (const op of bioc.data) {
// let m;
// if ((m = operators.find(o => o.easyCert?.id === op.id))) {
// m.bioc = op;
// } else if ((m = operators.find(o => o.easyCert?.idNr === op.details.idNr))) {
// m.bioc = op;
// } else if ((m = operators.find(o => o.bioc?.name === op.name && o.bioc?.details.address === op.details.address))) {
// m.bioc2 = op;
// } else {
// operators.push({bioc: op});
// }
// }
// for (const op of bioqs.data) {
// let m;
// if (op.lfbisNr && (m = operators.find(o => o.easyCert?.idNr === op.lfbisNr))) {
// m.bioqs = op;
// } else if (op.lfbisNr && (m = operators.find(o => o.bioc?.details.idNr === op.lfbisNr))) {
// m.bioqs = op;
// } else if (op.idNr && (m = operators.find(o => o.bioc?.details.idNr === op.idNr))) {
// m.bioqs = op;
// } else if ((m = operators.find(o => (o.easyCert?.name ?? o.bioc?.name) === op.name && (o.easyCert?.details.address ?? o.bioc?.details.address) === op.address))) {
// m.bioqs = op;
// } else {
// operators.push({bioqs: op});
// }
// }
// for (const op of lkv.data) operators.push({lkv: op});
//
// for (const op of operators) {
// op.name = op.easyCert?.name ?? op.bioc?.name ?? op.bioqs?.name ?? op.lkv?.name ?? null;
// op.address = op.easyCert?.details.address ?? op.bioc?.details.address ?? op.bioqs?.address ?? op.lkv?.address ?? null;
// op.postalCode = op.easyCert?.postalCode ?? op.bioc?.postalCode ?? op.bioqs?.postalCode ?? op.lkv?.postalCode ?? null;
// op.city = op.easyCert?.city ?? op.bioc?.city ?? op.bioqs?.city ?? op.lkv?.city ?? null;
// op.countryCode = op.easyCert?.countryCode ?? op.bioc?.details.countryCode ?? 'AT';
// op.lfbisNr = op.bioqs?.lfbisNr ?? null;
// const certNrs = [...new Set([].concat(
// op.easyCert?.details.certificates.map(c => c.nr) ?? [],
// op.bioc?.details.certificates.map(c => c.nr) ?? [],
// op.bioqs?.certificates.map(c => c.nr) ?? [],
// op.lkv?.certificates.map(c => c.nr) ?? []
// ))];
// op.ooId = certNrs
// .map(nr => nr.match(/[A-Z]{2}-.{3}-[0-9]{3}\.[0-9]{3}-[0-9]{7}\.[0-9]{4}\.[0-9]{3}/))
// .filter(nr => nr !== null)
// .map(nr => nr[0].split('.')[1])[0] ?? null;
// }
//
// operators.sort((a, b) => a.name.localeCompare(b.name));
$first = true;
echo "{\"data\":[\n";
foreach ($operators as $op) {
if (!$first) echo ",\n";
echo " ";
echo jenc($op);
$first = false;
}
echo "\n]}\n";
+2 -4
View File
@@ -1,6 +1,6 @@
<?php
$url = 'https://lkv.at/at/zertifizierung/themen/BIO/zertifizierte-BIO-Betriebe.php';
$url = 'https://lkv.at/at/zertifizierung/themen/bio/zertifizierte-BIO-Betriebe.php';
header('Access-Control-Allow-Origin: *');
header('Content-Type: application/json; charset=UTF-8');
@@ -25,8 +25,6 @@ if ($info !== '') {
exit;
}
header('Content-Type: application/json; charset=UTF-8');
$postalCode = '';
if (isset($_GET['postalCode']) && ctype_digit($_GET['postalCode'])) {
$postalCode = $_GET['postalCode'];
@@ -52,7 +50,7 @@ curl -s '$url' | grep -A3 '<tr' \
@buf = map { "\$_\n" } split /\n/, \$chunk;
} END { print for @buf; }' \
| grep -B3 -A1 --no-group-separator '"postalCode":"$postalCode' | grep -B2 -A2 --no-group-separator '"lfbisNr":$lfbisNr' \
| sed '\$s/.$//'
| sed '\$s/.$//' \
| sed 's@\s*",@",@g;s@:"\s*@:"@g'
EOF);
echo "]}\n";
+136
View File
@@ -0,0 +1,136 @@
<!DOCTYPE html>
<html lang="de">
<head>
<title>Bio Zertifikate</title>
<script>
async function operatorDetailsEasyCert(db, id) {
const res = await fetch(`/organic/external/easy-cert/operators/${db}:${id}`);
if (!res.ok) throw new Error(res.statusText);
return await res.json();
}
async function listOperatorsEasyCert(country, postalCode) {
const res = await fetch(`/organic/external/easy-cert/operators?country=${country}&postalCode=${postalCode}`);
if (!res.ok) throw new Error(res.statusText);
const data = await res.json();
const details = await Promise.all(data.data.map(op => operatorDetailsEasyCert(op.db, op.id)));
for (let i = 0; i < details.length; i++) {
data.data[i].details = details[i];
}
return data;
}
async function operatorDetailsBioc(id) {
const res = await fetch(`/organic/external/bioc/operators/${id}`);
if (!res.ok) throw new Error(res.statusText);
return await res.json();
}
async function listOperatorsBioc(country, postalCode) {
const res = await fetch(`/organic/external/bioc/operators?country=${country}&postalCode=${postalCode}`);
if (!res.ok) throw new Error(res.statusText);
const data = await res.json();
const details = await Promise.all(data.data.map(op => operatorDetailsBioc(op.id)));
for (let i = 0; i < details.length; i++) {
data.data[i].details = details[i];
}
return data;
}
async function listOperatorsBioQs(country, postalCode) {
const res = await fetch(`/organic/external/bioqs/operators?country=${country}&postalCode=${postalCode}`);
if (!res.ok) throw new Error(res.statusText);
return await res.json();
}
async function listOperatorsLkv(postalCode) {
const res = await fetch(`/organic/external/lkv/operators?postalCode=${postalCode}`);
if (!res.ok) throw new Error(res.statusText);
return await res.json();
}
async function listOperators(country, postalCode) {
const res = await fetch(`/organic/operators?country=${country}&postalCode=${postalCode}`);
if (!res.ok) throw new Error(res.statusText);
const operators = (await res.json()).data;
operators.sort((a, b) => (a.name ?? "").localeCompare(b.name));
console.log(...operators);
return operators;
}
async function operatorDetails(ooId) {
const res = await fetch(`/organic/operators/${ooId}`);
if (!res.ok) throw new Error(res.statusText);
return await res.json();
}
async function search(country, postalCode) {
for (const t of document.getElementsByClassName("operators")) document.body.removeChild(t);
for (const t of document.getElementsByClassName("certificates")) document.body.removeChild(t);
for (const t of document.getElementsByTagName("p")) document.body.removeChild(t);
const operators = await listOperators(country, postalCode);
const tbl = document.createElement("table");
tbl.className = "operators";
for (const op of operators) {
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${op.name ?? '-'}</td>
<td>${op.lfbisNr ?? '-'}</td>
<td>${op.id ?? '-'}</td>
<td>${op.address ?? '-'}</td>
<td>${op.postalCode ?? '-'}</td>
<td>${op.city ?? '-'}</td>
<td>${op.countryCode ?? '-'}</td>
<td><button onclick="details('${op.id}')">Details</button></td>`;
tbl.appendChild(tr);
}
document.body.appendChild(tbl);
}
async function details(ooId) {
for (const t of document.getElementsByTagName("p")) document.body.removeChild(t);
for (const t of document.getElementsByClassName("certificates")) document.body.removeChild(t);
const data = await operatorDetails(ooId)
console.log(data);
const p = document.createElement("p");
p.innerHTML = '';
document.body.appendChild(p);
for (const cert of data.certificates) {
p.innerHTML += `Zertifikat: ${cert.nr} ${cert.id} <a href="${cert.tracesPdfUrl}" target="_blank">Traces</a> / <a href="${cert.appendixPdfUrl}" target="_blank">Anhang</a> (${cert.validFrom}-${cert.validTo})<br/>`;
}
const certs = await Promise.all(data.certificates.map(async c => {
const res = await fetch(`/organic/pdf?format=json&url=${encodeURIComponent(c.pdfUrl)}`);
return await res.json();
}));
const tbl = document.createElement("table");
tbl.className = "certificates";
for (const cert of certs) {
if (cert.type === "unknown") continue;
console.log(cert);
const op = cert.operator;
const auth = cert.authority;
const tr = document.createElement("tr");
tr.innerHTML = `
<td>${cert.id ?? '-'}</td>
<td>${cert.nr ?? '-'}</td>
<td>${cert.validFrom}<br/>${cert.validTo}</td>
<td>${op.name}<br/>${op.address}<br/>${op.postalCode} ${op.city} (${op.countryCode})</td>
<td>${op.id ?? '-'}</td>`;
tbl.appendChild(tr);
}
document.body.appendChild(tbl);
}
</script>
</head>
<body>
<h1>Bio Zertifikate</h1>
<form onsubmit="search('AT', this.postalCode.value).then(); return false;">
<input type="text" name="postalCode" placeholder="PLZ"/>
<button type="submit">Suchen</button>
</form>
</body>
</html>
+2 -51
View File
@@ -36,13 +36,13 @@ $AUTHORITIES = [
];
header('Access-Control-Allow-Origin: *');
header('Content-Type: application/json; charset=UTF-8');
function jenc($data): string {
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
if ($_SERVER['REQUEST_METHOD'] !== 'GET' && $_SERVER['REQUEST_METHOD'] !== 'HEAD') {
header('Content-Type: application/json; charset=UTF-8');
header('Status: 405');
header('Content-Length: 56');
header('Allow: GET, HEAD');
@@ -58,44 +58,7 @@ if ($info !== '/' && str_ends_with($info, '/')) {
}
$parts = explode('/', $info);
if (str_starts_with($info, '/certificates/')) {
$id = $parts[2];
if (str_ends_with($id, '.txt')) {
$id = substr($id, 0, -4);
}
if (str_contains($id, '/') || !file_exists("certificates/$id.pdf")) {
header('Content-Type: application/json; charset=UTF-8');
header('Status: 404');
header('Content-Length: 22');
echo "{\"error\":\"not found\"}\n";
exit;
}
if (str_ends_with($parts[2], '.txt')) {
$mode = '-layout';
if (isset($_GET['raw']) && strtolower($_GET['raw']) === 'true') {
$mode = '-raw';
}
header('Content-Type: text/plain; charset=UTF-8');
system("pdftotext $mode 'certificates/$id.pdf' -");
exit;
}
if (str_ends_with($id, '.appendix')) {
header('Status: 303');
header('Location: ' . substr($id, 0, -9));;
exit;
}
$cert = shell_exec("pdftotext -raw 'certificates/$id.pdf' -");
$appendix = shell_exec("pdftotext -raw 'certificates/$id.appendix.pdf' -");
$p1 = strpos($cert, "\nI.3 ");
$p2 = strpos($cert, "\nI.4 ");
echo substr($cert, $p1 + 5, $p2 - $p1 - 5);
exit;
} else if ($info === '/authorities') {
header('Content-Type: application/json; charset=UTF-8');
if ($info === '/authorities') {
echo "{\"data\":[\n";
$first = true;
foreach ($AUTHORITIES as $auth) {
@@ -107,7 +70,6 @@ if (str_starts_with($info, '/certificates/')) {
exit;
} else if (str_starts_with($info, '/authorities/')) {
$code = $parts[2];
header('Content-Type: application/json; charset=UTF-8');
if (array_key_exists($code, $AUTHORITIES)) {
echo jenc($AUTHORITIES[$code]);
echo "\n";
@@ -117,17 +79,6 @@ if (str_starts_with($info, '/certificates/')) {
echo "{\"error\":\"not found\"}\n";
}
exit;
} else if ($info === '/operators') {
header('Content-Type: application/json; charset=UTF-8');
header('Status: 501');
header('Content-Length: 27');
echo "{\"error\":\"not implemented\"}\n";
exit;
} else if (str_starts_with($info, '/operators/')) {
$ooid = $parts[2];
header('Content-Type: text/plain; charset=UTF-8');
echo "Organic Operator Id: $ooid\n";
exit;
}
header('Status: 404');
View File
+346
View File
@@ -0,0 +1,346 @@
<?php
require "../.php/pdf.php";
header('Access-Control-Allow-Origin: *');
header('Content-Type: application/json; charset=UTF-8');
if ($_SERVER['REQUEST_METHOD'] !== 'GET' && $_SERVER['REQUEST_METHOD'] !== 'HEAD') {
header('Status: 405');
header('Content-Length: 56');
header('Allow: GET, HEAD');
echo "{\"error\":\"method not allowed\",\"allow\":[\"GET\",\"HEAD\"]}\n";
exit;
}
function jenc($data): string {
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
function jget($url): array | null {
$s = curl_init($url);
curl_setopt($s, CURLOPT_RETURNTRANSFER, true);
if (($json = curl_exec($s)) === false) {
return null;
}
return json_decode($json, true);
}
function pdf_fetch($url): array | null {
$s = curl_init("https://elwig.at/organic/pdf?format=json&url=" . urlencode($url));
curl_setopt($s, CURLOPT_RETURNTRANSFER, true);
if (($json = curl_exec($s)) === false) {
return null;
}
return json_decode($json, true);
}
function update_local(string $ooId, array $new): bool {
$existed = file_exists(".ids/$ooId.txt");
if (!($file = fopen(".ids/$ooId.txt", 'c+')))
return !$existed;
$data = [];
while (($line = fgets($file)) !== false) {
[$k,$v] = explode(':', trim($line), 2);
$data[$k] = $v;
}
foreach ($new as $k => $v) {
if ($v !== null)
$data[$k] = $v;
}
fseek($file, 0);
ftruncate($file, 0);
foreach ($data as $k => $v) {
fwrite($file, "$k:$v\n");
}
fclose($file);
return !$existed;
}
function find_local(string $anyId): string | null {
$found = [];
foreach (scandir('.ids/') as $file) {
if (!str_ends_with($file, '.txt')) continue;
$data = file_get_contents(".ids/$file");
if (str_contains($data, "\n$anyId\n"))
$found[] = substr($file, 0, strlen($file) - 4);
}
return sizeof($found) === 1 ? $found[0] : null;
}
function update_cache(string $country, string $postalCode): void {
$anyNew = false;
$traces = jget("https://webgate.ec.europa.eu/tracesnt/directory/publication/organic-operator/for/query?countryCode=$country&operatorPostalCode=$postalCode&sort=issuedOn");
foreach ($traces as $op) {
// traces
$ooId = $op['operatorIdentifier'];
$name = $op['operator']['name'];
$address = $op['operator']['address']['street']['value'];
$cityPostalCode = $op['operator']['address']['cityReference']['postalCode'];
$cityName = $op['operator']['address']['cityReference']['name'];
$new = update_local($ooId, ['plz' => $postalCode, 'name' => "$name|$address|$cityPostalCode $cityName"]);
$anyNew = $anyNew || $new;
}
if (!$anyNew) return;
$m = curl_multi_init();
$requests = [];
foreach (['easy-cert', 'bioc', 'bioqs', 'lkv'] as $api) {
$s = curl_init("https://elwig.at/organic/external/$api/operators?country=$country&postalCode=$postalCode");
$requests[] = $s;
curl_setopt($s, CURLOPT_RETURNTRANSFER, true);
curl_multi_add_handle($m, $s);
}
$running = 1;
do {
curl_multi_exec($m, $running);
if ($running) usleep(250_000);
} while ($running);
foreach ($requests as $r) {
curl_multi_remove_handle($m, $r);
}
curl_multi_close($m);
$data = [];
foreach ($requests as $s) {
if (($json = curl_multi_getcontent($s)) === null) {
header('Status: 500');
header('Content-Length: 34');
echo "{\"error\":\"internal server error\"}\n";
exit;
}
$data[] = json_decode($json, true)['data'];
}
foreach ($data[0] as $op) {
// easy-cert
$ooId = find_local("easy-cert:$op[db]:$op[id]");
$lfbisNr = null;
if ($ooId === null && ($opDetails = jget($op['url'])) !== null) {
foreach ($opDetails['certificates'] as $cert) {
if (preg_match("/\.([0-9]{3}-[0-9]{7})\./", $cert['nr'], $matches) === 1) {
$ooId = $matches[1];
}
}
if ($ooId === null || $lfbisNr === null) {
foreach ($opDetails['certificates'] as $cert) {
if ($cert['pdfUrl'] === null) continue;
$pdfDetails = pdf_fetch($cert['pdfUrl']);
if (isset($pdfDetails['operator']['lfbisNr'])) $lfbisNr = $pdfDetails['operator']['lfbisNr'];
if (!isset($pdfDetails['id'])) continue;
if (preg_match("/\.([0-9]{3}-[0-9]{7})\./", $pdfDetails['id'], $matches) === 1) {
$ooId = $matches[1];
}
}
}
}
if ($ooId === null)
continue;
update_local($ooId, ['plz' => $postalCode, 'lfbis' => $lfbisNr, 'easy-cert' => "$op[db]:$op[id]"]);
}
foreach ($data[1] as $op) {
// bioc
$ooId = find_local("bioc:$op[id]");
if ($ooId === null && ($opDetails = jget($op['url'])) !== null) {
foreach ($opDetails['certificates'] as $cert) {
if (preg_match("/\.([0-9]{3}-[0-9]{7})\./", $cert['nr'], $matches) === 1) {
$ooId = $matches[1];
}
}
if ($ooId === null) {
foreach ($opDetails['certificates'] as $cert) {
if ($cert['pdfUrl'] === null) continue;
$pdfDetails = pdf_fetch($cert['pdfUrl']);
if (isset($pdfDetails['operator']['lfbisNr'])) $op['lfbisNr'] = $pdfDetails['operator']['lfbisNr'];
if (!isset($pdfDetails['id'])) continue;
if (preg_match("/\.([0-9]{3}-[0-9]{7})\./", $pdfDetails['id'], $matches) === 1) {
$ooId = $matches[1];
}
}
}
}
if ($ooId === null)
continue;
update_local($ooId, ['plz' => $postalCode, 'bioc' => "$op[id]"]);
}
foreach ($data[2] as $op) {
// bioqs
$ooId = find_local("bioqs:$op[id]");
$lfbisNr = null;
if ($ooId === null || $lfbisNr === null) {
foreach ($op['certificates'] as $cert) {
if (preg_match("/\.([0-9]{3}-[0-9]{7})\./", $cert['nr'], $matches) === 1) {
$ooId = $matches[1];
}
}
if ($ooId === null || $lfbisNr === null) {
foreach ($op['certificates'] as $cert) {
if ($cert['pdfUrl'] === null) continue;
$pdfDetails = pdf_fetch($cert['pdfUrl']);
if (isset($pdfDetails['operator']['lfbisNr'])) $lfbisNr = $pdfDetails['operator']['lfbisNr'];
if (!isset($pdfDetails['id'])) continue;
if (preg_match("/\.([0-9]{3}-[0-9]{7})\./", $pdfDetails['id'], $matches) === 1) {
$ooId = $matches[1];
}
}
}
}
if ($ooId === null)
continue;
update_local($ooId, ['plz' => $postalCode, 'lfbis' => $lfbisNr, 'bioqs' => "$op[id]"]);
}
foreach ($data[3] as $op) {
// lkv
$ooId = find_local("lkv:$op[id]");
if ($ooId === null)
continue;
update_local($ooId, ['plz' => $postalCode, 'lfbis' => $op['lfbisNr'], 'lkv' => "$op[id]"]);
update_local($ooId, ['plz' => $postalCode, 'lfbis' => $op['lfbisNr'], 'lkv' => "$op[id]"]);
}
}
$info = substr($_SERVER['PATH_INFO'], 1);
if ($info === '') {
// search
$limit = $_GET['limit'] ?? null;
$offset = intval($_GET['offset'] ?? "0");
if ($limit === '') $limit = null;
if ($limit !== null) $limit = intval($limit);
$country = $_GET['country'] ?? null;
$postalCode = $_GET['postalCode'] ?? null;
if ($country === null || $postalCode === null) {
header('Status: 400');
header('Content-Length: 109');
echo "{\"error\":\"bad request\",\"message\":\"The 'country' and 'postalCode' request URL query parameters are required\"}\n";
exit;
}
update_cache($country, $postalCode);
echo "{\"data\":[";
if ($country === 'AT') {
$first = true;
foreach (scandir('.ids/') as $file) {
if (!str_ends_with($file, '.txt') || !str_starts_with($file, '040-')) continue;
$id = substr($file, 0, strlen($file) - 4);
$data = file_get_contents(".ids/$file");
if (str_starts_with($data, "plz:$postalCode\n")) {
preg_match("/^name:(.*?)\|(.*?)\|([0-9]{4}) (.*?)$/m", $data, $name);
preg_match("/^lfbis:(.*?)$/m", $data, $lfbis);
if (!$first) echo ",";
echo "\n ";
echo jenc([
'id' => $id,
'lfbisNr' => $lfbis[1],
'name' => $name[1],
'address' => $name[2],
'postalCode' => $name[3],
'city' => $name[4],
'country' => $country,
'url' => "https://elwig.at/organic/operators/$id",
]);
$first = false;
}
}
}
echo "\n]}\n";
exit;
}
if (preg_match("/^((easy-cert:[a-z]+|bioqs|bioc|lkv):[0-9a-zA-Z-_]+|lfbis:[0-9]+)$/", $info) === 1) {
$found = find_local($info);
if ($found !== null) {
header('Status: 303');
header('Location: /organic/operators/' . $found);
header('Content-Length: 0');
exit;
}
header('Status: 404');
header('Content-Length: 22');
echo "{\"error\":\"not found\"}\n";
exit;
} else if (!file_exists(".ids/$info.txt")) {
header('Status: 404');
header('Content-Length: 22');
echo "{\"error\":\"not found\"}\n";
exit;
}
$data = file_get_contents(".ids/$info.txt");
preg_match("/^name:(.*?)\|(.*?)\|([0-9]{4}) (.*?)$/m", $data, $name);
preg_match("/^lfbis:(.*?)$/m", $data, $lfbis);
preg_match("/^easy-cert:(.*?)$/m", $data, $easyCert);
preg_match("/^bioqs:(.*?)$/m", $data, $bioQs);
preg_match("/^bioc:(.*?)$/m", $data, $bioC);
if (sizeof($easyCert) > 1 && ($opDetails = jget("https://elwig.at/organic/external/easy-cert/operators/$easyCert[1]")) !== null) {
foreach ($opDetails['certificates'] as $cert) {
if ($cert['pdfUrl'] === null) continue;
pdf_fetch($cert['pdfUrl']);
}
}
if (sizeof($bioQs) > 1 && ($opDetails = jget("https://elwig.at/organic/external/bioqs/operators/$bioQs[1]")) !== null) {
foreach ($opDetails['certificates'] as $cert) {
if ($cert['pdfUrl'] === null) continue;
pdf_fetch($cert['pdfUrl']);
}
}
if (sizeof($bioC) > 1 && ($opDetails = jget("https://elwig.at/organic/external/bioc/operators/$bioC[1]")) !== null) {
foreach ($opDetails['certificates'] as $cert) {
if ($cert['pdfUrl'] === null) continue;
pdf_fetch($cert['pdfUrl']);
}
}
$certs = [];
$certIds = [];
foreach (scandir('../certificates/') as $file) {
if (!str_contains($file, ".$info."))
continue;
$certId = substr($file, 0, 31);
if (in_array($certId, $certIds))
continue;
$certIds[] = $certId;
$cert = parse_pdf("../certificates/$certId.pdf");
$appendix = parse_pdf("../certificates/$certId.appendix.pdf");
if ($cert === null && $appendix === null)
continue;
$parts = explode('.', $file);
$certs[] = [
'id' => $certId,
'nr' => $appendix['nr'] ?? null,
'validFrom' => $cert['validFrom'] ?? null,
'validTo' => $cert['validTo'] ?? null,
'authority' => ['code' => $parts[0]],
'operator' => ['id' => $parts[1]],
'url' => "https://elwig.at/organic/certificates/$certId",
'tracesPdfUrl' => $cert !== null ? "https://elwig.at/organic/certificates/$certId.pdf" : null,
'appendixPdfUrl' => $appendix !== null ? "https://elwig.at/organic/certificates/$certId.appendix.pdf" : null,
];
}
$details = [];
if (preg_match("/^easy-cert:(.*?)$/m", $data, $easyCert) === 1)
$details[] = "https://elwig.at/organic/external/easy-cert/operators/$easyCert[1]";
if (preg_match("/^bioc:(.*?)$/m", $data, $bioc) === 1)
$details[] = "https://elwig.at/organic/external/bioc/operators/$bioc[1]";
if (preg_match("/^bioqs:(.*?)$/m", $data, $bioqs) === 1)
$details[] = "https://elwig.at/organic/external/bioqs/operators/$bioqs[1]";
if (preg_match("/^lkv:(.*?)$/m", $data, $lkv) === 1)
$details[] = "https://elwig.at/organic/external/lkv/operators/$lkv[1]";
echo jenc([
'id' => $info,
'lfbisNr' => $lfbis[1],
'name' => $name[1],
'address' => $name[2],
'postalCode' => $name[3],
'city' => $name[4],
'country' => 'AT',
'url' => "https://elwig.at/organic/operators/$info",
'tracesUrl' => "https://webgate.ec.europa.eu/tracesnt/directory/publication/organic-operator/index#!?query=$info",
'originUrls' => $details,
'certificates' => $certs,
]);
+41 -181
View File
@@ -1,5 +1,7 @@
<?php
require '.php/pdf.php';
if ($_SERVER['REQUEST_METHOD'] !== 'GET' && $_SERVER['REQUEST_METHOD'] !== 'POST' && $_SERVER['REQUEST_METHOD'] !== 'HEAD') {
header('Status: 405');
header('Content-Length: 0');
@@ -17,25 +19,6 @@ if ($info !== '') {
$url = isset($_GET['url']) ? str_replace(' ', '+', $_GET['url']) : null;
$format = $_GET['format'] ?? 'json';
function get_address($array, $from, $to): array {
$address = [];
$postalCode = [];
$city = [];
for ($i = $to; $i >= $from; $i--) {
$el = $array[$i];
if (sizeof($postalCode) > 0) {
if (sizeof($address) === 0) $el = rtrim($el, ", \n\r\t\v\0");
if (strlen($el) === 0) continue;
array_unshift($address, $el);
} else if (preg_match("/^[A-Z0-9.\-]{3,},?$/", $el)) {
array_unshift($postalCode, trim($el, ", \n\r\t\v\0"));
} else {
array_unshift($city, $el);
}
}
return [implode(' ', $address), implode(' ', $postalCode), implode(' ', $city)];
}
function jenc($data): string {
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
@@ -47,7 +30,9 @@ if (!$file || !$headerfile) {
header('Content-Length: 0');
exit;
}
$filename = stream_get_meta_data($file)['uri'];
$cacheFile = null;
$tmpfilename = stream_get_meta_data($file)['uri'];
$filename = $tmpfilename;
$headerfilename = stream_get_meta_data($headerfile)['uri'];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stdin = fopen("php://input", "rb");
@@ -58,13 +43,30 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
fclose($stdin);
} else {
if (exec("curl -s -D " . escapeshellarg($headerfilename) . " -o " . escapeshellarg($filename) . " " . escapeshellarg($url)) === false) {
if ($cache = fopen('certificates/.cache.csv', 'r')) {
while (($line = fgets($cache)) !== false) {
$parts = explode(';', trim($line), 3);
$timestamp = intval($parts[0]);
if ($parts[2] !== $url) continue;
$cacheFile = 'certificates/' . $parts[1] . '.pdf';
break;
}
fclose($cache);
}
if ($cacheFile !== null) {
$filename = $cacheFile;
} else {
if (exec("curl -sk -D " . escapeshellarg($headerfilename) . " -o " . escapeshellarg($filename) . " " . escapeshellarg($url)) === false) {
header('Status: 500');
header('Content-Length: 0');
exit;
}
}
}
$timestamp = time();
if ($format === 'text') {
header('Content-Type: text/plain; charset=UTF-8');
passthru("pdftotext -raw " . escapeshellarg($filename) . " -");
@@ -73,176 +75,34 @@ if ($format === 'text') {
passthru("pdfsig " . escapeshellarg($filename));
} else if ($format === 'json') {
header('Content-Type: application/json; charset=UTF-8');
if (exec("pdftotext -raw " . escapeshellarg($filename) . " -", $text) === false) {
if (($cert = parse_pdf($filename, true)) === null) {
header('Status: 500');
header('Content-Length: 0');
exit;
}
$text = implode("\n", $text);
exec("pdfsig " . escapeshellarg($filename), $sig);
$sig = implode("\n", $sig);
$certType = $cert['type'];
$certId = $cert['id'];
$r = preg_match('@([a-z]{2}) (https://webgate\.ec\.europa\.eu/tracesnt/directory/publication/organic-operator/(.*?)\.pdf) (\d+) / (\d+)@', $text, $matches);
if ($r === 1) {
// TRACES certificate
$data = [];
$parts = preg_split('@\n(I+\.\d+) ([^\n]*)@', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
$status = str_replace("\n", '', $parts[0]);
for ($i = 3; $i < sizeof($parts); $i += 3) {
$data[$parts[$i - 2]] = trim($parts[$i]);
if ($certType === 'traces') {
if ($certId !== null && $url !== null && $cacheFile === null) {
copy($filename, "certificates/$certId.pdf");
file_put_contents('certificates/.cache.csv', "$timestamp;$certId;$url\n", FILE_APPEND);
}
$lang = $matches[1];
$splitAddr = [
'de' => 'Adresse',
'en' => 'Address',
][$lang];
$splitCountry = [
'de' => 'Land',
'en' => 'Country',
][$lang];
$statusMap = [
'de' => [
'AUSGESTELLT' => 'issued',
],
'en' => [
'ISSUED' => 'issued',
]
][$lang];
$activityMap = [
'de' => [
'Aufbereitung' => 'preparation',
'Ausfuhr' => 'export',
'Einfuhr' => 'import',
'Lagerung' => 'storing',
'Produktion' => 'production',
'Vertrieb' => 'distribution',
'Vertrieb/Inverkehrbringen' => 'distribution_placing_on_the_market',
],
'en' => [
'Distribution' => 'distribution',
'Distribution/Placing on the market' => 'distribution_placing_on_the_market',
'Export' => 'export',
'Import' => 'import',
'Preparation' => 'preparation',
'Production' => 'production',
'Storing' => 'storing',
],
][$lang];
$certUrl = $matches[2];
$certId = $matches[3];
$authorityId = explode('.', $certId)[0];
$operatorId = explode('.', $certId)[1];
$operator = preg_split('@\s+@', trim($data['I.3']));
$p1 = array_search($splitAddr, $operator);
$p2 = array_search($splitCountry, $operator);
$operatorName = trim(implode(' ', array_filter($operator, fn($k,$i) => $i > 0 && $i < $p1, ARRAY_FILTER_USE_BOTH)), ', ');
[$opAddr, $opPostal, $opCity] = get_address($operator, $p1 + 1, $p2 - 1);
$authority = preg_split('@\s+@', trim($data['I.4']));
$until = array_search("($authorityId)", $authority);
$p1 = array_search($splitAddr, $authority);
$p2 = array_search($splitCountry, $authority);
$authorityName = implode(' ', array_filter($authority, fn($k,$i) => $i > 0 && $i < $p1 - 1 && ($i !== $p1 - 2 || !str_starts_with($k, '(')), ARRAY_FILTER_USE_BOTH));
[$aAddr, $aPostal, $aCity] = get_address($authority, $p1 + 1, $p2 - 1);
$activities = [];
foreach (explode("\n", $data['I.5']) as $a) {
$activities[] = $activityMap[trim($a, '• ')];
}
preg_match_all('/\([a-g]\)/', $data['I.6'], $matches, PREG_SET_ORDER);
$products = [];
foreach ($matches as $m) {
$products[] = $m[0];
}
preg_match_all('@\d+/\d+/\d+@', $data['I.8'], $matches, PREG_SET_ORDER);
$valid1 = implode('-', array_reverse(explode('/', $matches[0][0])));
$valid2 = implode('-', array_reverse(explode('/', $matches[1][0])));
$sigs = [];
foreach (array_slice(explode("\nSignature #", $sig), 1) as $s) {
$sData = [];
$sData2 = [];
preg_match_all('/\n {2}- (([^:\n]*): )?([^\n]*)/', $s, $matches, PREG_SET_ORDER);
foreach ($matches as $m) {
if (strlen($m[2]) === 0) {
$sData2[] = $m[3];
} else {
$sData[$m[2]] = $m[3];
} else if ($certType !== 'unknown') {
if ($certId !== null && $url !== null && $cacheFile === null) {
copy($filename, "certificates/$certId.appendix.pdf");
file_put_contents('certificates/.cache.csv', "$timestamp;$certId.appendix;$url\n", FILE_APPEND);
}
}
$sigs[] = [
'signerCommonName' => $sData['Signer Certificate Common Name'],
'valid' => $sData['Signature Validation'] === 'Signature is Valid.',
'trusted' => $sData['Certificate Validation'] === 'Certificate is Trusted.',
'totalDocument' => in_array('Total document signed', $sData2),
'timestamp' => gmdate('Y-m-d\TH:i:s\Z', strtotime($sData['Signing Time'])),
'type' => $sData['Signature Type'],
'hashAlgorithm' => $sData['Signing Hash Algorithm'],
'signerDistinguishedName' => $sData['Signer full Distinguished Name'],
'fieldName' => $sData['Signature Field Name'],
];
}
echo "{\"type\":\"traces\",\"lang\":\"$lang\",\"id\":\"$certId\",\"status\":\"$statusMap[$status]\"";
echo ",\n \"operator\":{\"id\":" . jenc($operatorId).
',"groupOfOperators":' . jenc(!str_starts_with($data['I.2'], '☑')) .
',"name":' . jenc($operatorName) .
',"address":' . jenc($opAddr) .
',"postalCode":' . jenc($opPostal) .
',"city":' . jenc($opCity) .
',"countryCode":' . jenc($operator[sizeof($operator) - 1]) .
"},\n \"authority\":{\"id\":" . jenc($authorityId) .
',"name":' . jenc($authorityName) .
',"address":' . jenc($aAddr) .
',"postalCode":' . jenc($aPostal) .
',"city":' . jenc($aCity) .
',"countryCode":' . jenc($authority[sizeof($authority) - 1]) .
"},\n \"activities\":" . jenc($activities) .
",\n \"productCategories\":" . jenc($products) .
",\n \"validFrom\":" . jenc($valid1) .
',"validTo":' . jenc($valid2) .
",\n \"url\":\"$certUrl\"" .
",\n \"digitalSignatures\":" . jenc($sigs) .
"\n}\n";
echo jenc($cert);
exit;
}
if (preg_match('/AT-BIO-[0-9]{3}/', $text, $matches) === 1) {
$authorityId = $matches[0];
$certId = null;
$certNr = null;
if (preg_match("/$authorityId\.040-[0-9]{7}\.[0-9]{4}\.[0-9]{3}/", $text, $matches) === 1)
$certId = $matches[0];
if (preg_match_all("/\b[0-9]+([._-])[0-9]+\g{-1}[0-9]+\b/", $text, $matches, PREG_SET_ORDER) !== false) {
foreach ($matches as $m) {
if (strlen($m[0]) > 10 && !str_ends_with($certId, $m[0]))
$certNr = $m[0];
}
}
$operator = ['lfbisNr' => null];
if ($authorityId === 'AT-BIO-301') {
// TODO
} else if ($authorityId === 'AT-BIO-302') {
if (preg_match("/^lfbis[^0-9]*([0-9]{7})$/im", $text, $matches) === 1)
$operator['lfbisNr'] = $matches[1];
} else if ($authorityId === 'AT-BIO-401') {
// TODO
} else if ($authorityId === 'AT-BIO-402') {
// TODO
} else if ($authorityId === 'AT-BIO-903') {
// TODO
}
echo "{\"type\":\"$authorityId\",\"lang\":\"de\",\"id\":" . jenc($certId) . ",\"nr\":" . jenc($certNr);
echo ",\n \"operator\":" . jenc($operator) . ",\n \"authority\":{\"id\":" . jenc($authorityId) . "}}\n";
exit;
}
echo "{\"type\":\"unknown\"}\n";
} else if ($cacheFile === $filename) {
header('Content-Type: application/pdf');
header('Content-Length: ' . filesize($filename));
header('Content-Disposition: inline; filename="' . basename($filename) . '"');
readfile($filename);
} else {
$headers = [];
$status_code = null;