<?php
# encoding: utf-8
# api: php
# type: functions
# category: database
# title: Fossil utility code
# description: database and EXTCGI helper code
# version: 0.3
# 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()
# · has_cap("v")
# · fossil_exec(["tag", "list"])
# · config(meta($fn)) - PMD
#
#-- init
ini_set("display_errors", !empty($_REQUEST["dbg"]));
$_SERVER["FOSSIL_SELF"] = "https://$_SERVER[SERVER_NAME]$_SERVER[FOSSIL_URI]/";
define("FOSSIL_BIN", "fossil");
/**
* 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 = new PDO("sqlite:$_SERVER[FOSSIL_REPOSITORY]");
$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|ENT_HTML5, "utf-8");
}
$h = "h";
/**
* Test if active user had admin "s" capabilities.
*
* @return bool
*/
function is_admin() {
return has_cap("s");
}
/**
* Check if any of the listed capability abbreviations is present for user
*
* @param string $cap List of caps
* @return bool
*/
function has_cap($cap="v") {
return preg_match("~[$cap]~", $_SERVER["FOSSIL_CAPABILITIES"]);
}
/**
* Run fossil binary with arguments and automatic -R reference.
*
* @param string $cap List of caps
* @return bool
*/
function fossil_exec($args, $input=NULL) {
$args = array_map(
function ($s) {
return escapeshellarg(preg_replace("/[^\\r\\n\\t\\x20-\\xFF]/", "", $s));
},
array_merge(
["--nocgi"], array_filter($args, "strlen"), ["-R", $_SERVER["FOSSIL_REPOSITORY"]]
)
);
$cmd = FOSSIL_BIN . " " . implode(" ", $args) . " 2>&1";
if ($input) {
#$tmpfn = tempnam();
#$cmd .= " < $tmpfn";
}
exec($cmd, $stdout, $errno);
header("X-Debug: $errno: " . implode("//", $stdout));
return implode("\n", $stdout);
}
/**
* 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], array_map("trim", $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("~\{ ((?: [^\{\"\'\}]* | (?R) | \"[^\"]*\" | '[^']*' )+) \} | \< (.+?) \>~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;
}