diff --git a/.gitignore b/.gitignore index e4401d9..7491cbe 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ content/.user.ini content/php.ini custom/assets/petition-map-data.json custom/data/petition-map-cache.json +custom/data/publiser/ +content/publiser/.htpasswd diff --git a/Containerfile b/Containerfile index 60fe44c..dfc0b20 100644 --- a/Containerfile +++ b/Containerfile @@ -3,6 +3,13 @@ FROM php:8.4.14-apache # Enable Apache modules and custom config as root during build RUN a2enmod rewrite headers +# GD extension - used by /publiser to resize/recompress uploaded images +RUN apt-get update \ + && apt-get install -y --no-install-recommends libjpeg62-turbo-dev libpng-dev libwebp-dev \ + && docker-php-ext-configure gd --with-jpeg --with-webp \ + && docker-php-ext-install gd \ + && rm -rf /var/lib/apt/lists/* + COPY apache.conf /etc/apache2/conf-available/custom.conf RUN a2enconf custom diff --git a/compose.yaml b/compose.yaml index 694c31f..b8e0017 100644 --- a/compose.yaml +++ b/compose.yaml @@ -10,4 +10,4 @@ services: ports: - "4040:80" command: > - bash -c "chown -R www-data:www-data /var/www/custom/data /var/www/custom/assets && apache2-foreground" + bash -c "chown -R www-data:www-data /var/www/custom/data /var/www/custom/assets /var/www/html/nyheter /var/www/html/artikler && apache2-foreground" diff --git a/content/.htaccess b/content/.htaccess index 379c049..632ef79 100644 --- a/content/.htaccess +++ b/content/.htaccess @@ -2,8 +2,8 @@ DirectorySlash Off # Block direct access to content source files - # Allow only the entry point - + # Allow only the entry point, and the standalone /publiser admin app + Require all denied @@ -30,6 +30,13 @@ DirectorySlash Off RewriteCond %{REQUEST_URI} ^/app/ RewriteRule ^(.*)$ /index.php [L,QSA] + # Standalone /publiser admin app - bypass the CMS entirely, dispatch + # everything to its own entry point (static assets like .css/.js are + # excluded so Apache serves those files directly) + RewriteCond %{REQUEST_URI} ^/publiser + RewriteCond %{REQUEST_URI} !\.(css|js|png|jpe?g|webp|gif|svg)$ + RewriteRule ^publiser(/.*)?$ publiser/index.php [L,QSA] + # Don't rewrite if file exists RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ /index.php [L,QSA] diff --git a/content/.htaccess.base b/content/.htaccess.base index ffeacd3..042b87a 100644 --- a/content/.htaccess.base +++ b/content/.htaccess.base @@ -2,8 +2,8 @@ DirectorySlash Off # Block direct access to content source files - # Allow only the entry point - + # Allow only the entry point, and the standalone /publiser admin app + Require all denied @@ -30,6 +30,13 @@ DirectorySlash Off RewriteCond %{REQUEST_URI} ^/app/ RewriteRule ^(.*)$ /index.php [L,QSA] + # Standalone /publiser admin app - bypass the CMS entirely, dispatch + # everything to its own entry point (static assets like .css/.js are + # excluded so Apache serves those files directly) + RewriteCond %{REQUEST_URI} ^/publiser + RewriteCond %{REQUEST_URI} !\.(css|js|png|jpe?g|webp|gif|svg)$ + RewriteRule ^publiser(/.*)?$ publiser/index.php [L,QSA] + # Don't rewrite if file exists RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ /index.php [L,QSA] diff --git a/content/nyheter/2026-03-10-SPLEIS-forerkort-tingretten/10-spleis-into.md b/content/nyheter/2026-03-10-SPLEIS-forerkort-tingretten/10-spleis-into.md index 82252c6..0cee1a5 100644 --- a/content/nyheter/2026-03-10-SPLEIS-forerkort-tingretten/10-spleis-into.md +++ b/content/nyheter/2026-03-10-SPLEIS-forerkort-tingretten/10-spleis-into.md @@ -11,5 +11,3 @@ Uten førerkort blir hverdagen veldig vanskelig: matbutikk, legeavtaler og famil Denne rettssaken er en unik mulighet til å utfordre regelverket og skape reell endring. Ditt bidrag går direkte til rettssaken – en kamp for rettferdighet, medisinsk frihet og et verdig liv for alle pasienter. Bidra nå, og vær med på å endre regelverket! Gi ditt bidrag i dag – hver krone teller! Les Glenn Dahl [sin historie på NRK.no](https://www.nrk.no/norge/nektes-a-kjore-bil-fordi-han-bruker-medisinsk-cannabis-1.17210314). - - diff --git a/content/nyheter/2026-03-10-SPLEIS-forerkort-tingretten/20-spleis-embed.html b/content/nyheter/2026-03-10-SPLEIS-forerkort-tingretten/20-spleis-embed.html new file mode 100644 index 0000000..092bbfd --- /dev/null +++ b/content/nyheter/2026-03-10-SPLEIS-forerkort-tingretten/20-spleis-embed.html @@ -0,0 +1 @@ + diff --git a/content/publiser/.htaccess b/content/publiser/.htaccess new file mode 100644 index 0000000..693842f --- /dev/null +++ b/content/publiser/.htaccess @@ -0,0 +1,15 @@ +DirectorySlash On + +# Auth is checked in PHP (index.php), not via Apache AuthUserFile: relative +# AuthUserFile paths resolve against ServerRoot, not this directory, and +# that differs per environment (podman dev container vs cPanel). Some SAPIs +# (LSAPI/CGI/FastCGI, used on cPanel) strip the Authorization header from +# PHP by default, so forward it explicitly via an internal env var. + + RewriteEngine On + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + + + Require all denied + diff --git a/content/publiser/index.php b/content/publiser/index.php new file mode 100644 index 0000000..410f1c5 --- /dev/null +++ b/content/publiser/index.php @@ -0,0 +1,726 @@ + $v) { + $url .= '&' . urlencode($k) . '=' . urlencode((string)$v); + } + return $url; +} + +function publiserPublicUrl(string $section, string $folder): string { + return '/' . rawurlencode($section) . '/' . rawurlencode($folder) . '/'; +} + +function publiserImageUrl(string $location, string $section, string $folder, string $filename): string { + if ($location === 'live') { + return '/' . rawurlencode($section) . '/' . rawurlencode($folder) . '/' . rawurlencode($filename); + } + return publiserBaseUrl() . '?action=image&location=' . urlencode($location) + . '§ion=' . urlencode($section) . '&folder=' . urlencode($folder) . '&file=' . urlencode($filename); +} + +function publiserCheckCsrf(): void { + global $csrf; + $token = $_POST['csrf'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ''; + if (!hash_equals($csrf, $token)) { + http_response_code(403); + die('Ugyldig forespørsel (utløpt økt). Last siden på nytt og prøv igjen.'); + } +} + +function publiserRedirect(string $url): never { + header("Location: $url"); + exit; +} + +function publiserItemDir(string $location, string $section, string $folder): string { + return match ($location) { + 'live' => publiserLiveDir($section, $folder), + 'draft' => publiserDraftDir($section, $folder), + 'trash' => publiserTrashDir($section, $folder), + default => throw new RuntimeException('Ugyldig plassering'), + }; +} + +// --- minimal Markdown -> HTML for populating the WYSIWYG editor on load ---- +// (mirrors the bounded tag set publiser.js can serialize back to Markdown) + +function publiserEscapeInline(string $text): string { + return htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); +} + +function publiserInlineMdToHtml(string $text, callable $imageUrlResolver): string { + $text = publiserEscapeInline($text); + + $text = preg_replace_callback('/!\[(.*?)\]\((.*?)\)/', function ($m) use ($imageUrlResolver) { + $alt = $m[1]; + $file = $m[2]; + $realFile = html_entity_decode($file, ENT_QUOTES, 'UTF-8'); + $url = htmlspecialchars($imageUrlResolver($realFile), ENT_QUOTES, 'UTF-8'); + return '' . $alt . ''; + }, $text) ?? $text; + + $text = preg_replace_callback('/\[(.*?)\]\((.*?)\)/', function ($m) { + return '' . $m[1] . ''; + }, $text) ?? $text; + + $text = preg_replace('/\*\*(.+?)\*\*/', '$1', $text) ?? $text; + $text = preg_replace('/(?$1', $text) ?? $text; + + return $text; +} + +function publiserBlockMdToHtml(string $md, callable $imageUrlResolver): string { + $md = str_replace("\r\n", "\n", trim($md)); + if ($md === '') return '


'; + $blocks = preg_split('/\n{2,}/', $md) ?: []; + $html = []; + + foreach ($blocks as $block) { + $block = trim($block); + if ($block === '') continue; + $lines = explode("\n", $block); + + if (preg_match('/^<[a-zA-Z][a-zA-Z0-9-]*(\s[^>]*)?>/', $lines[0])) { + // Raw HTML embedded in markdown (e.g. an