Collection of themes/skins for the Fossil SCM

⌈⌋ branch:  Fossil Skins Extra


Artifact [a0b3b3e0e5]

Artifact a0b3b3e0e5e2436fcf553855a0458d1408b52bc4:

  • File extroot/fossil_common.php — part of check-in [c09979b7a9] at 2021-10-28 01:21:10 on branch trunk — Enable wikitag saving (via new `fossil_exec()`) (user: mario size: 5066)

<?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;
}