<?php
#
# OpenRat Content Management System
# Copyright (C) 2002-2009 Jan Dankert, hide@address.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
/**
* Wandelt eine Vorlage in ein PHP-Skript um.
*
* Die Vorlage wird gesparst, Elemente werden geladen und in die Zieldatei kopiert.
*
* @author Jan Dankert
* @package openrat.services
*/
class TemplateEngine
{
var $actualTagName = '';
/**
* Name Template.
*
* @var String
*/
var $tplName;
/**
* Erzeugt einen Templateparser.
*
* @param String $tplName Name des Templates, das umgewandelt werden soll.
*/
function TemplateEngine( $tplName='' )
{
$this->tplName = $tplName;
}
/**
* Wandelt eine Vorlage um
* @param filename Dateiname der Datei, die erstellt werden soll.
*/
function compile( $tplName = '')
{
if ( empty($tplName) )
$tplName = $this->tplName;
global $conf;
$srcOrmlFilename = 'themes/default/templates/'.$tplName.'.tpl.src.'.PHP_EXT;
$srcXmlFilename = 'themes/default/templates/'.$tplName.'.tpl.src.xml';
if ( is_file($srcOrmlFilename) )
$srcFilename = $srcOrmlFilename;
elseif ( is_file($srcXmlFilename) )
$srcFilename = $srcXmlFilename;
else
// Wenn Vorlage (noch) nicht existiert
die( get_class($this).': Template not found: "'.$tplName.'"' );
$filename = 'themes/default/pages/html/'.$tplName.'.tpl.'.PHP_EXT;
// Wenn Vorlage gaendert wurde, dann Umwandlung erneut ausf�hren.
if ( $conf['theme']['compiler']['cache'] && is_file($filename) && filemtime($srcFilename) <= filemtime($filename))
return;
if ( is_file($filename) && !is_writable($filename) )
die( get_class($this).': File is read-only: '.$filename);
Logger::debug("Compile template: ".$srcFilename.' to '.$filename);
// Vorlage und Zieldatei oeffnen
$document = $this->loadDocument( $srcFilename );
// Prüfen, ob Zielverzeichnis existiert, falls nicht: Anlegen.
if ( ! is_dir(dirname($filename)) )
{
$rc = mkdir( dirname($filename) );
if ( ! $rc )
Http::serverError('Unable to create directory: '.dirname($filename));
}
$outFile = @fopen($filename,'w');
if ( !is_resource($outFile) )
Http::serverError( get_class($this).': Unable to open file for writing: '.$filename);
$openCmd = array();
$depth = 0;
foreach( $document as $line )
{
// Initialisieren der m�glichen Element-Inhalte
$type = '';
$attributes = array();
$value = '';
$tag = '';
// Setzt: $tag, $attributes, $value, $type
extract( $line );
$this->actualTagName = $tag;
if ($type == 'complete' || $type == 'open')
$attributes = $this->checkAttributes($tag,$attributes);
if ( $type == 'open' )
$this->copyFileContents( $tag,$outFile,$attributes,++$depth );
elseif ( $type == 'complete' )
{
$this->copyFileContents( $tag ,$outFile,$attributes,$depth+1 );
$this->copyFileContents( $tag.'-end',$outFile,array() ,$depth+1 );
}
elseif ( $type == 'close' )
$this->copyFileContents( $tag.'-end',$outFile,array(),--$depth );
}
fclose($outFile);
// CHMOD ausf�hren.
if ( !empty($conf['theme']['compiler']['chmod']))
if ( !@chmod($filename,octdec($conf['theme']['compiler']['chmod'])) )
die( "CHMOD failed on file ".$filename );
}
function getElementValue( $elFilename,$attributes )
{
extract($attributes);
require($elFilename);
return $value;
}
function attributeValueOpenPHP($value)
{
$erg = $this->attributeValue($value);
// Für statische Texte muss kein PHP-Abschnitt geoeffnet werden.
if (substr($erg,0,1) == "'" && strpos($erg,'$')===FALSE )
return substr($erg,1,-1);
else
return '<'.'?php echo '.$erg.' ?>';
}
function attributeValue( $value )
{
$parts = explode( ':', $value, 2 );
if ( count($parts) < 2 )
$parts = array('',$value);
list( $type,$value ) = $parts;
$invert = '';
if ( substr($type,0,1)=='!' )
{
$type = substr($type,1);
$invert = '! ';
}
switch( $type )
{
case 'var':
return $invert.'$'.$value;
case 'text':
case '':
// Sonderf�lle f�r die Attributwerte "true" und "false".
// Hinweis: Die Zeichenkette "false" entspricht in PHP true.
// Siehe http://de.php.net/manual/de/language.types.boolean.php
if ( $value == 'true' || $value == 'false' )
return $value;
else
// macht aus "text1{var}text2" => "text1".$var."text2"
return "'".preg_replace('/{(\w+)\}/','\'.$\\1.\'',$value)."'";
case 'method':
return $invert.'$this->'.$value.'()';
case 'size':
return '@count($'.$value.')';
case 'property':
return $invert.'$this->'.$value;
case 'message':
// return 'lang('."'".$value."'".')';
// macht aus "text1{var}text2" => "text1".$var."text2"
return 'lang('."'".preg_replace('/{(\w+)\}/','\'.$\\1.\'',$value)."'".')';
case 'messagevar':
return 'lang($'.$value.')';
case 'mode':
return $invert.'$mode=="'.$value.'"';
case 'arrayvar':
list($arr,$key) = explode(':',$value.':none');
return $invert.'@$'.$arr.'['.$key.']';
case 'config':
$config_parts = explode('/',$value);
return $invert.'@$conf['."'".implode("'".']'.'['."'",$config_parts)."'".']';
default:
Http::serverError( get_class($this).': Unknown type "'.$type.'" in attribute. Allowed: var|method|property|message|messagevar|config or none');
}
}
/**
* Ein Baustein wird in die neue Vorlagedatei kopiert.
*/
function copyFileContents( $infile,$outFileHandler,$attr,$depth )
{
global $conf;
$hash = $depth;
$inFileName = OR_THEMES_DIR.$conf['interface']['theme'].'/include/html/'.$infile.'.inc.'.PHP_EXT;
$elFileName = OR_THEMES_DIR.$conf['interface']['theme'].'/include/html/'.$infile.'.el.' .PHP_EXT;
if ( !is_file($inFileName) )
if ( count($attr)==0 )
return;
else
// Baustein nicht vorhanden, Abbbruch.
die( get_class($this).': Compile failed, file not found: '.$inFileName );
$values = array();
foreach( $attr as $attrName=>$attrValue )
{
$values[] = "'".$attrName."'=>".$this->attributeValue($attrValue);
}
// Variablen $attr_* setzen
if ( count($attr) > 0 )
{
fwrite( $outFileHandler,'<?php ');
foreach( $attr as $attrName=>$attrValue )
fwrite( $outFileHandler,'$a'.$hash.'_'.$attrName."=".$this->attributeValue($attrValue).';');
fwrite( $outFileHandler,' ?>');
}
$file = file( $inFileName );
$ignore = false;
$linebreaks = true;
foreach( $file as $line )
{
// Ignoriere Zeilen, die fuer ein nicht vorhandenes Attribut gelten.
if ( strpos($line,'#IF-ATTR')!==FALSE )
{
$found = false;
foreach( $attr as $attrName=>$attrValue )
{
if ( strpos($line,'#IF-ATTR '.$attrName.'#')!==FALSE )
$found = true;
if ( strpos($line,'#IF-ATTR-VALUE '.$attrName.':'.$attrValue.'#')!==FALSE )
$found = true;
}
if ( ! $found )
$ignore = true;
}
// Nach einem IF-Block ertmal alles wieder anzeigen.
if ( strpos($line,'#END-IF')!==FALSE )
{
$ignore = false;
}
if ( strpos($line,'#ELSE')!==FALSE )
{
$ignore = !$ignore;
}
// Zeilenumbr�che nicht setzen.
if ( strpos($line,'#SET-LINEBREAK-OFF')!==FALSE )
$linebreaks = false;
// Zeilenumbr�che setzen.
if ( strpos($line,'#SET-LINEBREAK-OFF')!==FALSE )
$linebreaks = true;
// Ignoriere Zeilen, die zu ignorieren sind (logisch).
// Dies sind Blöcke, die nur fuer ein Attribut gueltig sind, welches
// aber nicht gesetzt ist.
if ( $ignore )
continue;
// Ignoriere Leerzeilen
if ( strlen(trim($line)) == 0)
continue;
// Ignoriere Kommentarzeilen
if ( in_array(substr(ltrim($line),0,2),array('//','/*','<!') ) || substr(ltrim($line),0,1) == '#')
continue;
if ( !$linebreaks )
$line = rtrim($line);
// Die Variablen "$attr_*" muessen pro Ebene eindeutig sein, daher wird an den
// Variablennamen die Tiefe angehangen.
$line = str_replace('$attr_','$a'.$hash.'_',$line);
foreach( $attr as $attrName=>$attrValue )
$line = str_replace('%'.$attrName.'%',$this->attributeValueOpenPHP($attrValue),$line);
fwrite( $outFileHandler,$line );
}
// Variablen $attr_* entfernen.
$unset_attr = array();
foreach( $attr as $attrName=>$attrValue )
$unset_attr[] = '$a'.$hash.'_'.$attrName;
if ( count($unset_attr) > 0 )
fwrite( $outFileHandler,'<?php unset('.implode(',',$unset_attr).') ?>');
if ( is_file($elFileName) )
{
fwrite( $outFileHandler, $this->getElementValue( $elFileName,$attr) );
}
}
/**
* Diese Funktion pr�ft, ob die Attribute zu einem Element g�ltig sind.<br>
* Falls ein ung�ltiges Attribut oder ein ung�ltiger Wert entdeckt wird,
* so wird das Skript abgebrochen.
* @return �berpr�fte und mit Default-Werten angereicherte Attribute
*/
function checkAttributes( $cmd,$attr )
{
global $conf;
$elements = parse_ini_file( OR_THEMES_DIR.$conf['interface']['theme'].'/include/elements.ini.'.PHP_EXT);
if ( !isset($elements[$cmd]) )
Http::serverError( get_class($this).': Parser error, unknown element "'.$cmd.'". Allowed: '.implode(',',array_keys($elements)) );
$checkedAttr = array();
// Schleife �ber alle Attribute.
foreach( explode(',',$elements[$cmd]) as $al )
{
$al=trim($al);
if ( $al=='')
continue; // Leeres Attribut... nicht zu gebrauchen.
$pair = explode(':',$al,2);
if ( count($pair) == 1 ) $pair[] = null;
list($a,$default) = $pair;
if ( is_string($default))
$default = str_replace('COMMA',',',$default); // Komma in Default-Werten ersetzen.
if ( isset($attr[$a]))
$checkedAttr[$a]=$attr[$a]; // Attribut ist bereits vorhanden, alles ok.
elseif ( $default=='*') // Pflichtfeld!
die( get_class($this).': Element "'.$cmd.'" needs the required attribute "'.$a.'"' );
elseif ( !is_null($default) )
$checkedAttr[$a]=$default;
else
;
unset( $attr[$a] ); // Damit am Ende das Urprungsarray leer wird.
}
if ( count($attr) > 0 )
{
foreach($attr as $name=>$value)
Http::serverError( get_class($this).': Unknown attribute "'.$name.'" in element "'.$cmd.'". Allowed: '.$elements[$cmd]."\n" );
}
return $checkedAttr;
}
/**
* Diese Funktion l�dt die passende Vorlagedatei.
*/
function loadDocument( $filename )
{
if ( substr($filename,-4)=='.xml')
return $this->loadXmlDocument( $filename );
else
return $this->loadOrmlDocument( $filename );
}
/**
* Laden und Parsen eines XML-Dokumentes.
*/
function loadXmlDocument( $filename )
{
$index = array();
$vals = array();
$p = xml_parser_create();
xml_parser_set_option ( $p, XML_OPTION_CASE_FOLDING,false );
xml_parser_set_option ( $p, XML_OPTION_SKIP_WHITE,false );
xml_parse_into_struct($p, implode('',file($filename)), $vals, $index);
xml_parser_free($p);
return $vals;
}
/**
* Laden und Parsen eines Dokumentes im Openrat-eigenem Format.<br>
* ("ORML"=Openrat Meta Language)
*/
function loadOrmlDocument( $filename )
{
$vals = array();
$openCmd = array();
foreach( file($filename) as $line )
{
$indent = strlen($line)-strlen(ltrim($line)); // Einzugstiefe
$line = trim($line); // Inhalt der Zeile ohne Einzug
if ( empty($line) ) // Leerzeilen in Vorlage
{
continue;
}
if ( substr($line,0,1)=='#' || substr($line,0,2)=='//')
continue;
$openCmdCopy = $openCmd;
krsort($openCmdCopy);
foreach($openCmdCopy as $idx=>$ccmd)
{
if ( $idx >= $indent )
{
$vals[] = array( 'tag' =>$ccmd,
'type' =>'close',
'value' =>'',
'attributes' => array(),
'level' => $indent );
unset($openCmd[$idx]);
}
}
// Zeile parsen
$li = explode(' ',$line);
$attr = array();
foreach( $li as $nr=>$part )
{
if ($nr==0)
$cmd = $part;
else
{
$el = explode(':',$part,2);
if ( count($el) < 2 )
die( 'parser error in line: '.$line );
list($a,$b) = $el;
$attr[$a]=$b;
}
}
// $cmd => enthaelt das Kommando
// $attr => enthaelt die Attribute
$openCmd[$indent]=$cmd;
$vals[] = array( 'tag'=>$cmd,
'type'=>'open',
'value'=>'',
'attributes'=>$attr,
'level'=>$indent );
}
// Am Ende der Datei alle offenen Tags schlie�en
$openCmdCopy = $openCmd;
krsort($openCmdCopy);
foreach($openCmdCopy as $idx=>$ccmd)
{
$vals[] = array( 'tag'=>$ccmd,
'type'=>'close',
'value'=>'',
'attributes'=>array(),
'level'=>$indent );
unset($openCmd[$idx]);
}
return $vals;
}
}
?>