Add CSS for fullscreen mode with CTA button Implement first name only display for map markers Add fullscreen toggle button with proper icons Update template to support fullpage mode Add new page with fullscreen map view
205 lines
8.2 KiB
PHP
205 lines
8.2 KiB
PHP
<?php
|
|
/**
|
|
* Petition Map Plugin (page plugin)
|
|
*
|
|
* Enable via metadata.ini: plugins = "petition-form, petition-map"
|
|
* Requires petition_id to be set (inherited from petition-form setup).
|
|
*
|
|
* Injects template variable $petition_map containing the full widget HTML.
|
|
* Embed in a content file: <?= $petition_map ?>
|
|
*
|
|
* Data flow:
|
|
* CSV -> petitionMapBuildData() -> cache (custom/data/) + public JSON (custom/assets/)
|
|
* Public JSON is served at /petition-map-data.json by the router's asset mechanism.
|
|
* JS fetches it and renders dots on a Leaflet map.
|
|
*
|
|
* Public JSON format:
|
|
* { "generated": int, "total": int, "regions": { "oslo": { "count": 42, "signers": [{"n":"Kari","a":false}] } } }
|
|
* n = first name (null for anonymous), a = anonymous (bool)
|
|
* No emails, surnames, tokens, or IP hashes ever leave the server.
|
|
*/
|
|
|
|
define('PETITION_MAP_CACHE_TTL', 60);
|
|
define('PETITION_MAP_CACHE_PATH', dirname(__DIR__, 2) . '/data/petition-map-cache.json');
|
|
define('PETITION_MAP_PUBLIC_PATH', dirname(__DIR__, 2) . '/assets/petition-map-data.json');
|
|
|
|
/**
|
|
* Build public-safe map data from the petition CSV.
|
|
*/
|
|
function petitionMapBuildData(string $csvPath): array {
|
|
$regions = [];
|
|
$total = 0;
|
|
|
|
if (!file_exists($csvPath)) {
|
|
return ['generated' => time(), 'total' => 0, 'regions' => (object)[]];
|
|
}
|
|
|
|
$fp = fopen($csvPath, 'r');
|
|
if (!$fp) {
|
|
return ['generated' => time(), 'total' => 0, 'regions' => (object)[]];
|
|
}
|
|
|
|
if (flock($fp, LOCK_SH)) {
|
|
fgetcsv($fp, null, ',', '"', '');
|
|
|
|
while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) {
|
|
if (!isset($row[6]) || $row[6] !== 'confirmed') continue;
|
|
|
|
$region = $row[4] ?? '';
|
|
$display = $row[5] ?? 'semi';
|
|
if (empty($region)) continue;
|
|
|
|
$anonymous = ($display === 'anonymous');
|
|
$name = $anonymous ? null : trim($row[2] ?? '');
|
|
if ($name !== null) {
|
|
// First name only — take the first word to keep map labels compact
|
|
$name = mb_substr(explode(' ', $name)[0], 0, 50);
|
|
if ($name === '') $name = null;
|
|
}
|
|
|
|
if (!isset($regions[$region])) {
|
|
$regions[$region] = ['count' => 0, 'signers' => []];
|
|
}
|
|
|
|
$regions[$region]['count']++;
|
|
$regions[$region]['signers'][] = ['n' => $name, 'a' => $anonymous];
|
|
$total++;
|
|
}
|
|
|
|
flock($fp, LOCK_UN);
|
|
}
|
|
fclose($fp);
|
|
|
|
return [
|
|
'generated' => time(),
|
|
'total' => $total,
|
|
'regions' => empty($regions) ? (object)[] : $regions,
|
|
];
|
|
}
|
|
|
|
function petitionMapReadCache(): ?array {
|
|
$path = PETITION_MAP_CACHE_PATH;
|
|
if (!file_exists($path)) return null;
|
|
if ((time() - filemtime($path)) > PETITION_MAP_CACHE_TTL) return null;
|
|
|
|
$json = file_get_contents($path);
|
|
if (!$json) return null;
|
|
|
|
$data = json_decode($json, true);
|
|
return is_array($data) ? $data : null;
|
|
}
|
|
|
|
function petitionMapWriteCache(array $data): void {
|
|
$json = json_encode($data, JSON_UNESCAPED_UNICODE);
|
|
if ($json === false) return;
|
|
|
|
foreach ([PETITION_MAP_CACHE_PATH, PETITION_MAP_PUBLIC_PATH] as $path) {
|
|
$dir = dirname($path);
|
|
if (!is_dir($dir)) {
|
|
mkdir($dir, 0750, true);
|
|
}
|
|
$tmp = $path . '.tmp.' . getmypid();
|
|
if (file_put_contents($tmp, $json, LOCK_EX) !== false) {
|
|
rename($tmp, $path);
|
|
}
|
|
}
|
|
}
|
|
|
|
function petitionMapGetData(string $csvPath): array {
|
|
$cached = petitionMapReadCache();
|
|
if ($cached !== null) return $cached;
|
|
|
|
$data = petitionMapBuildData($csvPath);
|
|
petitionMapWriteCache($data);
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Render the map widget HTML block.
|
|
*/
|
|
function petitionMapRenderWidget(Context $ctx, bool $fullpage = false): string {
|
|
$langPrefix = $ctx->get('langPrefix', '');
|
|
|
|
// CSS hash for cache busting
|
|
$cssPath = dirname(__DIR__, 2) . '/assets/petition-map.css';
|
|
$cssHash = file_exists($cssPath) ? substr(hash_file('md5', $cssPath), 0, 8) : '';
|
|
|
|
$jsPath = dirname(__DIR__, 2) . '/assets/petition-map.js';
|
|
$jsHash = file_exists($jsPath) ? substr(hash_file('md5', $jsPath), 0, 8) : '';
|
|
|
|
$dataJsonPath = PETITION_MAP_PUBLIC_PATH;
|
|
$dataHash = file_exists($dataJsonPath) ? substr(hash_file('md5', $dataJsonPath), 0, 8) : '';
|
|
|
|
$html = '';
|
|
|
|
// Leaflet CSS
|
|
$html .= '<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="anonymous">';
|
|
|
|
// Map CSS
|
|
$html .= '<link rel="stylesheet" href="/petition-map.css?v=' . $cssHash . '">';
|
|
|
|
// Map container
|
|
$sectionClass = 'petition-map-section escape' . ($fullpage ? ' petition-map-section--fullpage' : '');
|
|
$html .= '<section class="' . $sectionClass . '">';
|
|
$html .= '<h1 id="petition-map-count" class="petition-map-count" aria-live="polite"></h1>';
|
|
$html .= '<div id="petition-map" aria-label="Kart over underskrifter i Norge">';
|
|
$html .= '<div class="map-loading" id="map-loading" aria-live="polite">Laster kart…</div>';
|
|
$iconExpand = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="10 2 14 2 14 6"/><polyline points="6 14 2 14 2 10"/><line x1="14" y1="2" x2="9" y2="7"/><line x1="2" y1="14" x2="7" y2="9"/></svg>';
|
|
if (!$fullpage) {
|
|
$html .= '<a href="' . $langPrefix . '/underskriftskampanje/medisinsk-cannabis-pa-resept/kart/" class="petition-map-fullscreen-btn" title="Fullskjerm" aria-label="Åpne kart i fullskjerm">' . $iconExpand . '</a>';
|
|
}
|
|
if ($fullpage) {
|
|
$iconCollapse = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/><line x1="10" y1="14" x2="3" y2="21"/><line x1="21" y1="3" x2="14" y2="10"/></svg>';
|
|
$html .= '<a href="' . $langPrefix . '/underskriftskampanje/medisinsk-cannabis-pa-resept/#petition-map" class="petition-map-fullscreen-btn petition-map-fullscreen-btn--minimize" title="Minimer" aria-label="Gå tilbake til underskriftssiden">' . $iconCollapse . '</a>';
|
|
}
|
|
$html .= '</div>';
|
|
$html .= '<a href="' . $langPrefix . '/underskriftskampanje/medisinsk-cannabis-pa-resept/#sign-now" class="petition-map-cta-btn">Signer nå!</a>';
|
|
$html .= '</section>';
|
|
|
|
// Config for JS
|
|
$html .= '<script>';
|
|
$html .= 'window.PETITION_MAP_CONFIG={';
|
|
$html .= 'dataUrl:"/petition-map-data.json?v=' . $dataHash . '",';
|
|
$html .= 'geojsonUrl:"/norway-fylker.geojson",';
|
|
$html .= 'anonIconUrl:"/anon.svg",';
|
|
$html .= 'pollInterval:60000';
|
|
$html .= '};';
|
|
$html .= '</script>';
|
|
|
|
// Leaflet JS + map JS
|
|
$html .= '<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin="anonymous"></script>';
|
|
$html .= '<script src="/petition-map.js?v=' . $jsHash . '"></script>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
// --- Hook ---
|
|
|
|
Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
|
|
// Resolve page directory and metadata (reuse petition-form helpers if available)
|
|
if (!function_exists('petitionResolvePageDir')) return $vars;
|
|
$pageDir = petitionResolvePageDir($ctx);
|
|
if (!$pageDir) return $vars;
|
|
|
|
$metadata = loadMetadata($pageDir);
|
|
$plugins = $metadata['plugins'] ?? '';
|
|
if (strpos($plugins, 'petition-map') === false) return $vars;
|
|
|
|
$petitionId = $metadata['petition_id']
|
|
?? (function_exists('petitionGetIdFromPath') ? petitionGetIdFromPath($pageDir) : null);
|
|
if (!$petitionId) return $vars;
|
|
|
|
$petitionId = preg_replace('/[^a-z0-9\-_]/i', '', $petitionId);
|
|
if (empty($petitionId)) return $vars;
|
|
|
|
$csvPath = dirname(__DIR__, 2) . "/data/petitions/{$petitionId}.csv";
|
|
|
|
// Refresh cache + public JSON if stale
|
|
petitionMapGetData($csvPath);
|
|
|
|
// Inject widget HTML as template variable
|
|
$fullpage = !empty($metadata['map_fullpage']);
|
|
$vars['petition_map'] = petitionMapRenderWidget($ctx, $fullpage);
|
|
|
|
return $vars;
|
|
});
|