<?php
# encoding: utf-8
# api: php
# type: functions
# category: database
# title: Fossil utility code
# description: database and EXTCGI helper code
# version: 0.1
# state: alpha
# config: -
#
# Combines database and IO utility code for extroot/ scripts.
#
# ยท db("SELECT * FROM config WHERE name=?", ["col"])
# ยท get_config("project-description", "โฆ")
# ยท h("html context output")
# ยท is_admin()
# ยท config(meta($fn)) - PMD
#
#-- init
ini_set("display_errors", !empty($_REQUEST["dbg"]));
$_SERVER["FOSSIL_SELF"] = "https://$_SERVER[SERVER_NAME]$_SERVER[FOSSIL_URI]/";
/**
* Database query shorthand. (Using active fossil repository.)
*
* @param string $sql Query with placeholders
* @param array $params Bound parameters
* @param bool $fetch Immediate ->fetchAll()
* @return array|PDOStatement|PDO
*/
function db($sql="", $params=[], $fetch=TRUE) {
static $db;
if (empty($db)) {
if (!preg_match("~^/\w[/\w.-]+\w\.(fs?l?|fossil|sqlite)$~", $_SERVER["FOSSIL_REPOSITORY"])) {
die("db(): FOSSIL_REPOSITORY doesn't look right. Abort.");
}
$db = new PDO("sqlite::memory:");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$db->query("ATTACH DATABASE '$_SERVER[FOSSIL_REPOSITORY]' AS 'repo'");
}
if ($params) {
$stmt = $db->prepare($sql);
$stmt->execute($params);
return $fetch ? $stmt->fetchAll(PDO::FETCH_ASSOC) : $stmt;
}
elseif ($sql) {
return $db->query($sql)->fetchAll(PDO::FETCH_ASSOC);
}
else {
return $db;
}
}
/**
* Query fossil `config` table.
*
* @param string $name Option
* @param array $default Fallback
* @return string
*/
function get_config($name, $default) {
$r = db("SELECT value FROM config WHERE name=?", [$name]);
return $r ? $r[0]["value"] : $default;
}
/**
* HTML escape shorthand (for interpolation {$h(expr)} in strings).
*
* @param string $s Raw string
* @return string
*/
function h($s) {
return htmlspecialchars($s, ENT_QUOTES, "utf-8");
}
$h = "h";
/**
* Test if active user had admin "s" capabilities.
*
* @return bool
*/
function is_admin() {
return strpos($_SERVER["FOSSIL_CAPABILITIES"], "s") !== false;
}
/**
* Extract PMD (plugin meta data) from file.
* Multilang, pluginconf-derived, but still fairly crude.
*
* @param string $fn Source file
* @return array
*/
function meta($fn) {
$src = file_get_contents($fn, false, NULL, 0, 4096);
$src = preg_replace("~^#!.+|\R~", "\n", $src); # clean CRLF / shebang
preg_match_all("~(^\h{0,4}(#|//|/?\*).*\n)+~m", $src, $uu); # get comments (โ not consecutive)
$src = preg_replace("~^\h{0,4}(#|//|/?\*)\h{0,3}\r*~m", "", implode("", $uu[0])); # strip any #// prefix
preg_match_all("~^([\w-]+):(.*$\\n(?:(?![\w-]+:).+$\\n)*)~m", $src, $uu); # extract fields (multiline)
$r = array_combine($uu[1], $uu[2]);
array_change_key_case($r); # โ still unmapped hyphens
if (count($doc = preg_split("~\R\h*\R~", trim($src), 2)) == 2) { ; # comment block
$r["__doc__"] = $doc[1];
}
return $r;
}
/**
* PMD config: list extraction.
*
* @param array $meta From meta()
* @return array
*/
function config($meta, $r=[]) {
if (empty($meta["config"])) {
return [];
}
preg_match_all("~\{ (.+?) \} | \< (.+?) \>~x", $meta["config"], $def); # iterate over each {โฆ} block
foreach (array_merge($def[1], $def[2]) as $row) if ($row) {
preg_match_all("~ [\"':$]?(\w+)[\"']? \s*[:=]+\s* (?: \"([^\"]*)\" | '([^']*)' | ([^,]*) )~x", $row, $kv, PREG_SET_ORDER);
$opt = []; # visit each key:value pair
foreach ($kv as $f) {
$f = array_values(array_filter($f, "strlen"));
$opt[$f[1]] = isset($f[2]) ? $f[2] : "";
}
$r[] = $opt;
}
return $r;
}