<?

######################   Template Class ####################
#
# Met de class Template is het mogelijk om in een template bestand bepaalde variable-waarden te plaatsen.
# dit maakt het makkelijker voor disgners om een layout te veranderen zonder php te hoeven gebruiken.
# De template file kan elk normaal ascii bestand zijn en kan bestaan uit HTML, gewone tekst etc etc.
# Het model wat word vervangen heeft het volgende formaat: %[(a-z)]%
#
# Vereiste variabelen:
# $vars --  Een array gevuld met sleutel waarden die in het template bestand moeten worden
#           vervangen en de daarbijbehordende nieuwe waarden.
#
# Het systeem is:
#   - Hoofdletter ongevoelig,
#   - Alfanumerieke tekens en _ worden geaccepteerd,
#
# Vb.:
# $var = new Template("textfile.template.html");
# print $var->parse(    // aanroep om "dailer.template.htm" te parsen
#       array(
#           "link" => $templateLink,    // in het template bestand word "%[link]% vervangen door de waarde in $templateLink
#       )
#   );
#
######
# Door:     Willem de Vries
# Date:     9 februari 2005
# Voor:     Virtual Pc Services
# Versie:   2.2
#
######
# Changelog:
#    1.0 (WdV 7-11-2003): statische functie ombouwen naar object
#    1.1 (WdV 13-2-2004): parsen moet ook zonder parameters kunnen
#    1.2 (JF  15-2-2004): Set functie toegevoegd om variablen aan template toe te voegen, aaroep: set(variablenaam, value)
#    1.3 (JF  24-3-2004): Endless loop bug gefixed, bij openen van template kwam hij bij lege file of file zonder
#                         regeleinde niet bij eof bij lezen van filesize.
#    1.4 (WdV 04-3-2005): Nieuwe functie om default-waardes uit een HTML-comment veld te halen.
#    2.0 (WdV 09-2-2005): grotendeels herschreven refresh() en parse() routine. Nu 50% sneller!
#    2.1 (WdV 15-2-2005): Mogelijkheid om een template aan te maken vanuit een string ipv. een file uit te lezen
#    2.2 (WdV 28-10-2005): On-the-fly compressie om geheugen te sparen, kleinere array properties
#    2.3 (WdV 15-12-2005): Gebruik van de "TEMPLATE_SEARCH" superglobal om een (relatief) zoekpad op te geven waarin
#                          templates gezocht moeten worden.
###############################################################

function _slashjoin() {
    $out = array();
    foreach(func_get_args() as $param) {
        if ($param)
            $out[] = $param;
    }
    return preg_replace('#([/]+)#', '/', join('/', $out));
}

function _dirlist($str, $regex = '.*') {
    $out = array();

    $dh = (is_dir($str)) ? @opendir($str) : null;

    while ($dh and $fil = readdir($dh)) {
        $out[] = _slashjoin($str, $fil);
    }

    if ( !$out) return;

    list($item) = array_values(preg_grep("|$regex|i", $out));

#    error_log(__FUNCTION__ . " Matching items for |$regex|");
#    error_log(__FUNCTION__ . " Subdirs in $str: " . join('|', $out));
#    error_log(__FUNCTION__ . " Found dir '$item'");

    return ($item) ? $item : $str;

}


class Template {

    var $fn = null;
    var $filename = null;
    var $ch = null;
    var $error = null;
    var $searchpath = null;
    var $searchdir = null;

    var $inf = null;
    var $def = null;
    var $me = 'object';

    var $vars = array();

    function Template($name) {

        # Compatibility
        $this->filename =& $this->fn;
        $this->me = strtoupper(get_class($this));
        $this->searchpath = $GLOBALS[$this->me . "_SEARCH"];

        if (defined($this->me . "_COMPRESS")) {

            foreach(array(array('gzcompress', 'gzuncompress'), array('gzdeflate', 'gzinflate')) as $grp) {
                if (function_exists($grp[0])) {
                    $this->def = $grp[0];
                    $this->inf = $grp[1];
                    break;
                }
            }
        }

        $this->fn = $name;
        $this->refresh();
    }

    function set($varname, $value = ''){
        $this->vars[$varname] = $value;
    }


    function get($varname) {
        return $this->vars[$varname];
    }

    function refresh() {
        if (!preg_match('/\.([^\.\/\s]+)$/', $this->fn, $found) ) {

             $this->cached($this->fn);
             $this->fn = null;
             $this->error = null;
             return;
        }

        # Implementatie zoekpad
        $subdirs = preg_split('/([\s]*;[\s]*)/', $this->searchpath);

        if (! $subdirs)
            $subdirs = array('');

        list($a, $up, $b, $path) = preg_match('/^(([\.]+\/)*)(.+)/', $this->fn, $found) ? $found : array('', '', '', $found[0]);

        foreach($subdirs as $subdir) {

            $sub = _slashjoin($up, $subdir);

            # Wanneer de basis zoekdirectory niet bestaat, dan controleren we nog even
            # of deze naam niet partieel voorkomt ergens in de boom. Zie functie _dirlist().

            if ($subdir and !is_dir($sub)) {
                $sub = _dirlist(dirname($sub), preg_replace('/([^0-9a-z]+)$/', '', $subdir) );
            }

            $this->searchdir = _slashjoin($sub, $path);

            $exists = (file_exists($this->searchdir) and filesize($this->searchdir) > 0);

            if ($exists) {
                $this->searchpath = $sub;
                break;
            }
        }

        $readable = ($exists and is_readable($this->searchdir) );
        $soort = ($readable) ? filetype($this->searchdir) : "";

        if ($readable and $soort == "file") {

            $this->cached( join('', file($this->searchdir)) );
            $this->error = null;

        } elseif (!$exists) {

            $this->error( sprintf("'%s' bestaat niet.", $this->searchdir) );
        } elseif (!$readable) {

            $this->error( sprintf("'%s' kan niet worden gelezen.", $this->searchdir) );

        } elseif ($exists && $soort != "file") {
            $this->error( sprintf("'%s' is geen geldig bestand; %s", $this->searchdir, $soort) );
        }

        return;
    }

    function error($str) {

        $text = sprintf('%s %s [in %s]', $this->me, $str, $_SERVER['SCRIPT_NAME']);
        $this->error = $text;

        if ($_SERVER['IS_DEVEL'] and $this->error) error_log($this->error);
    }


    function parse($vars = array()) {

        $vars = $vars + $this->vars;

        if ($this->searchpath)
            # Voeg een slash toe aan het einde van het pad - templates verwachten dit!
            $vars['_searchpath'] = trim(_slashjoin($this->searchpath, ' '));

        $match = array();
        $repl = array();

        foreach(array_keys($vars) as $key){

            $match[] = sprintf("/(%%\[%s\]%%)/i", preg_quote($key));
            $repl[] = $vars[$key];
        }
        $match[] = '/%\[[^\[\]]+\]%/';   # Catch all
        $repl[] = '';

        return preg_replace($match, $repl, $this->cached());
    }

    function extractData() {

        $reg = '/<\!--(\s*([^=\s\<\>]+)[\s=]+[\'"]?([^\'"\s\<\>]+))+\s*-->/';

        $comment = '/<\!--\s*(.+)\s*-->/';
        $fields = '/([^=\s<>]+)[\s=]+([\'\"])?([^\s<>\2]+)/';

        if (! preg_match_all($comment, $this->cached(), $found) )
            return false;

        foreach($found[1] as $line) {

            preg_match_all($fields, $line, $res);

            for($i=0; $i < sizeof($res[0]); $i++) {
                $this->vars[$res[1][$i]] = $res[3][$i];
            }

        }
        return $this->vars;
    }

    function cached($str = null) {

            if (is_null($str)) {
                #request
                $do = $this->inf;
                return ($this->compressed() and $this->ch) ? $do($this->ch) : $this->ch;
            }

            $do = $this->def;
            $this->ch = ($this->compressed()) ? $do($str, 9) : $str;
    }

    function compressed() {
        return ($this->inf || $this->def);

    }
}

# EOF
?>
