X-Git-Url: http://git.shiar.nl/minimedit.git/blobdiff_plain/1abb286e88defee799e43cdd296914f74f722bde..HEAD:/article.inc.php diff --git a/article.inc.php b/article.inc.php index bc8dbff..093852f 100644 --- a/article.inc.php +++ b/article.inc.php @@ -1,25 +1,40 @@ 0 ? $monthname[intval($parts[1])] : '', $parts[0], - count($parts) > 5 ? "$parts[3]:$parts[4]" : '', - ])); -} - class ArchiveArticle { + public $raw, $title, $body; # file contents + public $meta = []; # head metadata properties + public $place = []; # template variables replaced in render() + public $api = FALSE; # requested programming interface + function __construct($path) { - $this->page = $path; - $this->link = preg_replace('{(?:/index)?\.html$}', '', $path); + $this->file = preg_replace('{^\.(?:/|$)}', '', $path); + $this->link = preg_replace('{(?:(?:/|^)index)?\.html$}', '', $this->file); + $this->raw($this->file); + } + + function raw($page) + { + if (!file_exists($page)) { + return; + } + $this->raw = file_get_contents($page); + + if (preg_match_all('{ + \G \s* + }x', $this->raw, $meta)) { + $matchlen = array_sum(array_map('strlen', $meta[0])); + $this->raw = substr($this->raw, $matchlen); # delete matched contents + $this->meta = array_combine($meta[1], $meta[2]); # [property => content] + } + + // find significant contents + $this->body = preg_replace('{}s', '', $this->raw); + if (preg_match('{

(.*?)

\s*(.*)}s', $this->body, $titlematch)) { + list (, $this->title, $this->body) = $titlematch; + } + + return $this->raw; } function __get($col) @@ -27,32 +42,60 @@ class ArchiveArticle return $this->$col = $this->$col(); # run method and cache } - function file() + function handler() { - if (!file_exists($this->page)) return; - return fopen($this->page, 'r'); + $path = $this->link; + $this->path = ''; + $this->restricted = FALSE; + while (TRUE) { + if (file_exists("$path/.private") and !$this->restricted) { + $this->restricted = $path; + } + + if (file_exists("$path/index.php")) { + return $path; + } + + $up = strrpos($path, '/'); + $this->path = substr($path, $up) . $this->path; + $path = substr($path, 0, $up); + if ($up === FALSE) { + break; + } + } + return; } - function rawtitle() + function index($api = TRUE) { - return fgets($this->file); + $this->handler; + if (empty($this->handler)) { + return; + } + $this->api = $api; + $Page = $this; + global $User; + return require "./{$this->handler}/index.php"; } - function title() + + function restricted() { - return preg_replace('{

(.*)

\s*}', '\1', $this->rawtitle); + $this->handler; + return $this->restricted; } + function safetitle() { - return trim(strip_tags($this->title)); + return trim($this->meta['og:title'] ?? strip_tags($this->title)); } function name() { - return $this->safetitle ?: $this->link; + return $this->safetitle ?: htmlspecialchars($this->link); } function last() { - return filemtime($this->page); + return filemtime($this->file); } function lastiso() { @@ -61,7 +104,7 @@ class ArchiveArticle function dateparts() { - preg_match('< / (\d{4}) [/-] (\d{2}) (?:- (\d{2}) )? - >x', $this->page, $ymd); + preg_match('< / (\d{4}) [/-] (\d{2}) (?:- (\d{2}) )? - >x', $this->file, $ymd); array_shift($ymd); return $ymd; } @@ -69,21 +112,11 @@ class ArchiveArticle { return implode('-', $this->dateparts()) . 'T12:00:00+02:00'; } - function date() - { - return showdate($this->dateparts); - } - function body() - { - if (!$this->file) return; - $this->rawtitle; - return fread($this->file, filesize($this->page) ?: 1); - } function story() { if ( preg_match('{ - \n (?: < (?: p | figure [^>]* ) >\s* )+ (]*>) | \n + (?: < (?: p | figure [^>]* ) >\s* )+ (]*>) | \n }x', $this->body, $img, PREG_OFFSET_CAPTURE) ) { # strip part after matching divider (image) if (isset($img[1])) { @@ -94,13 +127,17 @@ class ArchiveArticle return $this->body; } - function raw() - { - return $this->rawtitle . $this->body; - } function teaser() { - if (preg_match('{

(.*?)

}s', $this->story, $bodyp)) { + if ($override = @$this->meta['og:description']) { + # prefer specific page description if found in metadata + return $override; + } + + # paragraph contents following the page header if any + if (preg_match('{ + \G (?> \s+ |
]*> | \[\[[^]]*\]\] )*

\s* (.*?)

+ }sx', $this->body, $bodyp, 0)) { return $bodyp[1]; } } @@ -113,6 +150,11 @@ class ArchiveArticle } function image() { + if ($override = @$this->meta['og:image']) { + # prefer specific page image if found in metadata + return $override; + } + if ( preg_match('/\bsrc="([^"]*)"/', $this->img, $src) ) { return $src[1]; } @@ -120,9 +162,133 @@ class ArchiveArticle function thumb($size = '300x') { if (!$this->image or $this->image[0] !== '/') return; + if (preg_match('{^/thumb/\D}', $this->image)) { + return ltrim($this->image, '/'); + } return preg_replace( - ['{^(?:/thumb/[^/]*)?}', '/\.groot(?=\.\w+$)/'], ["thumb/$size", ''], - $this->image + ['{^(?:/thumb/[^/]*)?}', '/\.groot(?=\.\w+$)/', '/(?:\.jpg)?$/'], + [ "thumb/$size", '', '.jpg' ], + $this->image, 1 ); } + + function widget($name, $params = []) + { + $path = stream_resolve_include_path("widget/$name.php"); + if (!file_exists($path)) { + return ''.$name.' ontbreekt'; + } + + ob_start(); + $Page = clone $this; + if (is_array($params)) { + $Page->place += $params; + } + else { + foreach (explode(' ', $params) as $param) { + if ($set = strpos($param, '=')) { + $Page->place[ substr($param, 0, $set) ] = urldecode(substr($param, $set + 1)); + } + elseif (!empty($param)) { + $Page->place[] = $param; + } + } + } + + try { + include "widget/$name.php"; + return ob_get_clean(); + } + catch (Exception $e) { + return sprintf('%s', + "fout in $name: {$e->getMessage()}" + ); + } + } + + function render() + { + $doc = $this->raw; + + if (!empty($this->place['warn'])) { + $warn = '

[[warn]]

'; + if ($offset = strpos($doc, '')) { + $doc = substr_replace($doc, "\n\n".$warn, $offset + 5, 0); + } + else { + $doc = $warn . "\n\n" . $doc; + } + } + + # keep either login or logout parts depending on user level + global $User; + $userexists = $User && property_exists($User, 'login') && $User->login; + if (! ($userexists and !empty($this->editable)) ) { + # remove matching elements until first corresponding closing tag + $hideclass = $userexists ? 'logout' : 'login'; + $tagmatch = '<([a-z]+) class="'.$hideclass.'"[^>]*>'; + $doc = preg_replace("{\s*{$tagmatch}.*?}s", '', $doc); + } + + return preg_replace_callback( + '{ \[\[ ([^] ]+) ([^]]*) \]\] }x', + function ($sub) { + list ($placeholder, $name, $params) = $sub; + $html = $this->place[$name] ?? + $this->widget($name, $params); + if (empty($html) or $html[0] != '<') { + $html = "$html"; + } + $attr = sprintf(' data-dyn="%s"', is_numeric($name) ? '' : $name.$params); + # contents with identifier in first tag + return preg_replace( '/(?=>)/', $attr, $html, 1); + }, + $doc + ); + } +} + +class PageSearch +{ + public $handlers = []; + + function __construct($path = '.') + { + $this->iterator = new RecursiveCallbackFilterIterator( + new RecursiveDirectoryIterator($path), + function ($current) { + if ($current->getFilename()[0] === '.') { + # skip hidden files and directories + return FALSE; + } + if (file_exists($current->getFilename() . '/index.php')) { + # contents better provided by handler code + $this->handlers[ $current->getPathname() ] = $current; + return FALSE; + } + if ($current->isLink()) { + # ignore symlinks, original contents only + return FALSE; + } + if ($current->isDir()) { + # traverse subdirectories unless untracked in any amount + return !file_exists("$current/.gitignore"); + } + # match **/*.html + return preg_match('/(?getFilename()); + } + ); + } + + function files() + { + # order alphabetically by link + $dir = []; + foreach (new RecursiveIteratorIterator($this->iterator) as $name) { + $article = new ArchiveArticle($name); + $dir[$article->link] = $article; + } + ksort($dir); + return $dir; + } }