\n" );
+ }
+ $this->_added( $add ); # If any leftovers
+ }
+}
+
+?>
diff --git a/inc/auth.php b/inc/auth.php
new file mode 100644
index 000000000..825ecb9d2
--- /dev/null
+++ b/inc/auth.php
@@ -0,0 +1,290 @@
+
+require_once("inc/common.php");
+require_once("inc/io.php");
+# load the the auth functions
+require_once('inc/auth_'.$conf['authtype'].'.php');
+
+# some ACL level defines
+define('AUTH_NONE',0);
+define('AUTH_READ',1);
+define('AUTH_EDIT',2);
+define('AUTH_CREATE',4);
+define('AUTH_UPLOAD',8);
+define('AUTH_GRANT',255);
+
+if($conf['useacl']){
+ auth_login($_REQUEST['u'],$_REQUEST['p']);
+ # load ACL into a global array
+ $AUTH_ACL = file('conf/acl.auth');
+}
+
+/**
+ * This tries to login the user based on the sent auth credentials
+ *
+ * The authentication works like this: if a username was given
+ * a new login is assumed and user/password are checked - if they
+ * are correct a random authtoken is created which is stored in
+ * the session _and_ in a cookie.
+ * The user stays logged in as long as the session and the cookie
+ * match. This still isn't the securest method but requires an
+ * attacker to steal an existing session _and_ the authtoken
+ * cookie. The actual password is only transfered once per login.
+ *
+ * On a successful login $_SERVER[REMOTE_USER] and $USERINFO
+ * are set.
+*/
+function auth_login($user,$pass){
+ global $USERINFO;
+ global $conf;
+ global $lang;
+ $cookie = $_COOKIE['AUTHTOKEN'];
+ $session = $_SESSION[$conf['title']]['authtoken'];
+
+ if(isset($user)){
+ if (auth_checkPass($user,$pass)){
+ //make username available as REMOTE_USER
+ $_SERVER['REMOTE_USER'] = $user;
+ //set global user info
+ $USERINFO = auth_getUserData($user);
+ //set authtoken
+ $token = md5(uniqid(rand(), true));
+ $_SESSION[$conf['title']]['user'] = $user;
+ $_SESSION[$conf['title']]['authtoken'] = $token;
+ setcookie('AUTHTOKEN', $token);
+ }else{
+ //invalid credentials - log off
+ msg($lang['badlogin'],-1);
+ auth_logoff();
+ }
+ }elseif(isset($cookie) && isset($session)){
+ if($cookie == $session){
+ //make username available as REMOTE_USER
+ $_SERVER['REMOTE_USER'] = $_SESSION[$conf['title']]['user'];
+ //set global user info
+ $USERINFO = auth_getUserData($_SERVER['REMOTE_USER']);
+ }else{
+ //bad token
+ auth_logoff();
+ }
+ }else{
+ //just to be sure
+ auth_logoff();
+ }
+}
+
+/**
+ * This clears all authenticationdata and thus log the user
+ * off
+ */
+function auth_logoff(){
+ global $conf;
+ global $USERINFO;
+ unset($_SESSION[$conf['title']]['authtoken']);
+ unset($_SESSION[$conf['title']]['user']);
+ unset($_SERVER['REMOTE_USER']);
+ $USERINFO=null;
+}
+
+/**
+ * Convinience function for auth_aclcheck
+ */
+function auth_quickaclcheck($id){
+ global $conf;
+ global $USERINFO;
+ # if no ACL is used always return upload rights
+ if(!$conf['useacl']) return AUTH_UPLOAD;
+ return auth_aclcheck($id,$_SERVER['REMOTE_USER'],$USERINFO['grps']);
+}
+
+/**
+ * Returns the maximum rights a user has for
+ * the given ID or its namespace
+ */
+function auth_aclcheck($id,$user,$groups){
+ global $conf;
+ global $AUTH_ACL;
+
+ # if no ACL is used always return upload rights
+ if(!$conf['useacl']) return AUTH_UPLOAD;
+
+ $ns = getNS($id);
+ $perm = -1;
+
+ if($user){
+ //prepend groups with @
+ for($i=0; $i $perm){
+ $perm = $acl[2];
+ }
+ }
+ if($perm > -1){
+ //we had a match - return it
+ return $perm;
+ }
+ }
+
+ //still here? do the namespace checks
+ if($ns){
+ $path = $ns.':\*';
+ }else{
+ $path = '\*'; //root document
+ }
+
+ do{
+ $matches = preg_grep('/^'.$path.'\s+('.$regexp.')\s+/',$AUTH_ACL);
+ if(count($matches)){
+ foreach($matches as $match){
+ $match = preg_replace('/#.*$/','',$match); //ignore comments
+ $acl = preg_split('/\s+/',$match);
+ if($acl[2] > $perm){
+ $perm = $acl[2];
+ }
+ }
+ //we had a match - return it
+ return $perm;
+ }
+
+ //get next higher namespace
+ $ns = getNS($ns);
+
+ if($path != '\*'){
+ $path = $ns.':\*';
+ if($path == ':\*') $path = '\*';
+ }else{
+ //we did this already
+ //looks like there is something wrong with the ACL
+ //break here
+ return $perm;
+ }
+ }while(1); //this should never loop endless
+}
+
+/**
+ * Create a pronouncable password
+ *
+ * @see: http://www.phpbuilder.com/annotate/message.php3?id=1014451
+ */
+function auth_pwgen(){
+ $pw = '';
+ $c = 'bcdfghjklmnprstvwz'; //consonants except hard to speak ones
+ $v = 'aeiou'; //vowels
+ $a = $c.$v; //both
+
+ //use two syllables...
+ for($i=0;$i < 2; $i++){
+ $pw .= $c[rand(0, strlen($c)-1)];
+ $pw .= $v[rand(0, strlen($v)-1)];
+ $pw .= $a[rand(0, strlen($a)-1)];
+ }
+ //... and add a nice number
+ $pw .= rand(10,99);
+
+ return $pw;
+}
+
+/**
+ * Sends a password to the given user
+ *
+ * returns true on success
+ */
+function auth_sendPassword($user,$password){
+ global $conf;
+ global $lang;
+ $users = auth_loadUserData();
+ $hdrs = '';
+
+ if(!$users[$user]['mail']) return false;
+
+ $text = rawLocale('password');
+ $text = str_replace('@DOKUWIKIURL@',getBaseURL(true),$text);
+ $text = str_replace('@FULLNAME@',$users[$user]['name'],$text);
+ $text = str_replace('@LOGIN@',$user,$text);
+ $text = str_replace('@PASSWORD@',$password,$text);
+ $text = str_replace('@TITLE@',$conf['title'],$text);
+
+ if (!empty($conf['mailfrom'])) {
+ $hdrs = 'From: '.$conf['mailfrom']."\n";
+ }
+ return @mail($users[$user]['mail'],$lang['regpwmail'],$text,$hdrs);
+}
+
+/**
+ * The new user registration - we get our info directly from
+ * $_POST
+ *
+ * It returns true on success and false on any error
+ */
+function register(){
+ global $lang;
+ global $conf;
+
+ if(!$_POST['save']) return false;
+ if(!$conf['openregister']) return false;
+
+ //clean username
+ $_POST['login'] = preg_replace('/.*:/','',$_POST['login']);
+ $_POST['login'] = cleanID($_POST['login']);
+ //clean fullname and email
+ $_POST['fullname'] = trim(str_replace(':','',$_POST['fullname']));
+ $_POST['email'] = trim(str_replace(':','',$_POST['email']));
+
+ if( empty($_POST['login']) ||
+ empty($_POST['fullname']) ||
+ empty($_POST['email']) ){
+ msg($lang['regmissing'],-1);
+ return false;
+ }
+
+ //check mail
+ if(!isvalidemail($_POST['email'])){
+ msg($lang['regbadmail'],-1);
+ return false;
+ }
+
+ //okay try to create the user
+ $pass = auth_createUser($_POST['login'],$_POST['fullname'],$_POST['email']);
+ if(empty($pass)){
+ msg($lang['reguexists'],-1);
+ return false;
+ }
+
+ //send him the password
+ if (auth_sendPassword($_POST['login'],$pass)){
+ msg($lang['regsuccess'],1);
+ return true;
+ }else{
+ msg($lang['regmailfail'],-1);
+ return false;
+ }
+}
+
+/**
+ * Uses a regular expresion to check if a given mail address is valid
+ *
+ * @see http://www.webmasterworld.com/forum88/135.htm
+ *
+ * May not be completly RFC conform!
+ */
+function isvalidemail($email){
+ return eregi("^[0-9a-z]([-_.]?[0-9a-z])*@[0-9a-z]([-.]?[0-9a-z])*\\.[a-z]{2,4}$", $email);
+}
+
+?>
diff --git a/inc/auth_ldap.php b/inc/auth_ldap.php
new file mode 100644
index 000000000..2872e710f
--- /dev/null
+++ b/inc/auth_ldap.php
@@ -0,0 +1,137 @@
+
diff --git a/inc/auth_mysql.php b/inc/auth_mysql.php
new file mode 100644
index 000000000..213c4a852
--- /dev/null
+++ b/inc/auth_mysql.php
@@ -0,0 +1,98 @@
+ no need to ask for results
+ if ($result != 1) {
+ for($i=0; $i< mysql_num_rows($result); $i++) {
+ $temparray = mysql_fetch_assoc($result);
+ $resultarray[]=$temparray;
+ }
+ mysql_free_result ($result);
+ }
+ if (mysql_insert_id($link)) {
+ $resultarray = mysql_insert_id($link); //give back ID on insert
+ }
+ mysql_close ($link);
+ return $resultarray;
+}
+
+/**
+ * required auth function
+ *
+ * Checks if a user with the given password exists
+ */
+function auth_checkPass($user,$pass){
+ global $conf;
+ $cnf = $conf['auth']['mysql'];
+
+ $sql = str_replace('%u',addslashes($user),$cnf['passcheck']);
+ $sql = str_replace('%p',addslashes($pass),$sql);
+ $result = auth_mysql_runsql($sql);
+ return(count($result));
+}
+
+/**
+ * Required auth function
+ *
+ * Returns info about the given user needs to contain
+ * at least these fields:
+ *
+ * name string full name of the user
+ * mail string email addres of the user
+ * grps array list of groups the user is in
+ *
+ */
+function auth_getUserData($user){
+ global $conf;
+ $cnf = $conf['auth']['mysql'];
+
+ $sql = str_replace('%u',addslashes($user),$cnf['userinfo']);
+ $result = auth_mysql_runsql($sql);
+ if(!count($result)) return false;
+ $info = $result[0];
+
+ $sql = str_replace('%u',addslashes($user),$cnf['groups']);
+ $result = auth_mysql_runsql($sql);
+ if(!count($result)) return false;
+ foreach($result as $row){
+ $info['grps'][] = $row['group'];
+ }
+
+ return $info;
+}
+
+/**
+ * Required auth function
+ *
+ * Not implemented
+ */
+function auth_createUser($user,$name,$mail){
+ msg("Sorry. Creating users is not supported by the MySQL backend, yet",-1);
+ return null;
+}
+
+?>
diff --git a/inc/auth_plain.php b/inc/auth_plain.php
new file mode 100644
index 000000000..2b45b94ca
--- /dev/null
+++ b/inc/auth_plain.php
@@ -0,0 +1,100 @@
+
diff --git a/inc/common.php b/inc/common.php
new file mode 100644
index 000000000..c64d08898
--- /dev/null
+++ b/inc/common.php
@@ -0,0 +1,831 @@
+
+require_once("conf/dokuwiki.php");
+require_once("inc/io.php");
+
+//set up error reporting to sane values
+error_reporting(E_ALL ^ E_NOTICE);
+
+//make session rewrites XHTML compliant
+ini_set('arg_separator.output', '&');
+
+//init session
+session_name("DokuWiki");
+session_start();
+
+//kill magic quotes
+if (get_magic_quotes_gpc()) {
+ if (!empty($_GET)) remove_magic_quotes($_GET);
+ if (!empty($_POST)) remove_magic_quotes($_POST);
+ if (!empty($_COOKIE)) remove_magic_quotes($_COOKIE);
+ if (!empty($_REQUEST)) remove_magic_quotes($_REQUEST);
+ if (!empty($_SESSION)) remove_magic_quotes($_SESSION);
+ ini_set('magic_quotes_gpc', 0);
+}
+set_magic_quotes_runtime(0);
+ini_set('magic_quotes_sybase',0);
+
+function remove_magic_quotes(&$array) {
+ foreach (array_keys($array) as $key) {
+ if (is_array($array[$key])) {
+ remove_magic_quotes($array[$key]);
+ }else {
+ $array[$key] = stripslashes($array[$key]);
+ }
+ }
+}
+
+//disable gzip if not available
+if($conf['usegzip'] && !function_exists('gzopen')){
+ $conf['usegzip'] = 0;
+}
+
+/* ---------------------------------------------------------------------------------- */
+
+/**
+ * This returns the full absolute URL to the directory where
+ * DokuWiki is installed in (includes a trailing slash)
+ */
+function getBaseURL($abs=false){
+ global $conf;
+ //if canonical url enabled always return absolute
+ if($conf['canonical']) $abs = true;
+
+ //relative URLs are easy
+ if(!$abs){
+ $dir = dirname($_SERVER['PHP_SELF']).'/';
+ $dir = preg_replace('#//#','/',$dir);
+ $dir = preg_replace('#\/$#','/',$dir); #bugfix for weird WIN behaviour
+ return $dir;
+ }
+
+ $port = ':'.$_SERVER['SERVER_PORT'];
+ //remove port from hostheader as sent by IE
+ $host = preg_replace('/:.*$/','',$_SERVER['HTTP_HOST']);
+
+ // see if HTTPS is enabled - apache leaves this empty when not available,
+ // IIS sets it to 'off', 'false' and 'disabled' are just guessing
+ if (preg_match('/^(|off|false|disabled)$/i',$_SERVER['HTTPS'])){
+ $proto = 'http://';
+ if ($_SERVER['SERVER_PORT'] == '80') {
+ $port='';
+ }
+ }else{
+ $proto = 'https://';
+ if ($_SERVER['SERVER_PORT'] == '443') {
+ $port='';
+ }
+ }
+ $dir = (dirname($_SERVER['PHP_SELF'])).'/';
+ $dir = preg_replace('#//#','/',$dir);
+ $dir = preg_replace('#\/$#','/',$dir); #bugfix for weird WIN behaviour
+
+ return $proto.$host.$port.$dir;
+}
+
+/**
+ * Returns info about the current document as associative
+ * array.
+ */
+function pageinfo(){
+ global $ID;
+ global $REV;
+ global $USERINFO;
+ global $conf;
+
+ if($_SERVER['REMOTE_USER']){
+ $info['user'] = $_SERVER['REMOTE_USER'];
+ $info['userinfo'] = $USERINFO;
+ $info['perm'] = auth_quickaclcheck($ID);
+ }else{
+ $info['user'] = '';
+ $info['perm'] = auth_aclcheck($ID,'',null);
+ }
+
+ $info['namespace'] = getNS($ID);
+ $info['locked'] = checklock($ID);
+ $info['filepath'] = realpath(wikiFN($ID,$REV));
+ $info['exists'] = @file_exists($info['filepath']);
+ if($REV && !$info['exists']){
+ //check if current revision was meant
+ $cur = wikiFN($ID);
+ if(@file_exists($cur) && (@filemtime($cur) == $REV)){
+ $info['filepath'] = realpath($cur);
+ $info['exists'] = true;
+ $REV = '';
+ }
+ }
+ if($info['exists']){
+ $info['writable'] = (is_writable($info['filepath']) &&
+ ($info['perm'] >= AUTH_EDIT));
+ }else{
+ $info['writable'] = ($info['perm'] >= AUTH_CREATE);
+ }
+ $info['editable'] = ($info['writable'] && empty($info['lock']));
+ $info['lastmod'] = @filemtime($info['filepath']);
+
+ return $info;
+}
+
+/**
+ * adds a message to the global message array
+ *
+ * Levels can be:
+ *
+ * -1 error
+ * 0 info
+ * 1 success
+ */
+function msg($message,$lvl=0){
+ global $MSG;
+ $errors[-1] = 'error';
+ $errors[0] = 'info';
+ $errors[1] = 'success';
+
+ if(!isset($MSG)) $MSG = array();
+ $MSG[]=array('lvl' => $errors[$lvl], 'msg' => $message);
+}
+
+/**
+ * This builds the breadcrumbstrail and returns it as array
+ */
+function breadcrumbs(){
+ global $ID;
+ global $ACT;
+ global $conf;
+ $crumbs = $_SESSION[$conf['title']]['bc'];
+
+ //first visit?
+ if (!is_array($crumbs)){
+ $crumbs = array();
+ }
+ //we only save on show and existing wiki documents
+ if($ACT != 'show' || !@file_exists(wikiFN($ID))){
+ $_SESSION[$conf['title']]['bc'] = $crumbs;
+ return $crumbs;
+ }
+ //remove ID from array
+ $pos = array_search($ID,$crumbs);
+ if($pos !== false && $pos !== null){
+ array_splice($crumbs,$pos,1);
+ }
+
+ //add to array
+ $crumbs[] =$ID;
+ //reduce size
+ while(count($crumbs) > $conf['breadcrumbs']){
+ array_shift($crumbs);
+ }
+ //save to session
+ $_SESSION[$conf['title']]['bc'] = $crumbs;
+ return $crumbs;
+}
+
+/**
+ * This is run on a ID before it is outputted somewhere
+ * currently used to replace the colon with something else
+ * on Windows systems and to have proper URL encoding
+ */
+function idfilter($id){
+ global $conf;
+ if ($conf['useslash'] && $conf['userewrite']){
+ $id = strtr($id,':','/');
+ }elseif (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' &&
+ $conf['userewrite']) {
+ $id = strtr($id,':',';');
+ }
+ $id = urlencode($id);
+ $id = str_replace('%3A',':',$id); //keep as colon
+ $id = str_replace('%2F','/',$id); //keep as slash
+ return $id;
+}
+
+/**
+ * This builds a link to a wikipage (using getBaseURL)
+ */
+function wl($id='',$more='',$script='doku.php',$canonical=false){
+ global $conf;
+ $more = str_replace(',','&',$more);
+
+ $id = idfilter($id);
+ $xlink = getBaseURL($canonical);
+
+ if(!$conf['userewrite']){
+ $xlink .= $script;
+ $xlink .= '?id='.$id;
+ if($more) $xlink .= '&'.$more;
+ }else{
+ $xlink .= $id;
+ if($more) $xlink .= '?'.$more;
+ }
+
+ return $xlink;
+}
+
+/**
+ * Just builds a link to a script
+ */
+function script($script='doku.php'){
+ $link = getBaseURL();
+ $link .= $script;
+ return $link;
+}
+
+/**
+ * Return namespacepart of a wiki ID
+ */
+function getNS($id){
+ if(strpos($id,':')!==false){
+ return substr($id,0,strrpos($id,':'));
+ }
+ return false;
+}
+
+/**
+ * Returns the id without the namespace
+ */
+function noNS($id){
+ return preg_replace('/.*:/','',$id);
+}
+
+/**
+ * Checks the wikitext against a list of blocked expressions
+ * returns true if the text contains any bad words
+ */
+function checkwordblock(){
+ global $TEXT;
+ global $conf;
+
+ if(!$conf['usewordblock']) return false;
+
+ $blocks = file('conf/wordblock.conf');
+ $re = array();
+ #build regexp from blocks
+ foreach($blocks as $block){
+ $block = preg_replace('/#.*$/','',$block);
+ $block = trim($block);
+ if(empty($block)) continue;
+ $re[] = $block;
+ }
+ if(preg_match('#('.join('|',$re).')#si',$TEXT)) return true;
+ return false;
+}
+
+/**
+ * Returns the IP of the client including X-Forwarded-For
+ * Proxy Headers
+ */
+function clientIP(){
+ $my = $_SERVER['REMOTE_ADDR'];
+ if($_SERVER['HTTP_X_FORWARDED_FOR']){
+ $my .= ' ('.$_SERVER['HTTP_X_FORWARDED_FOR'].')';
+ }
+ return $my;
+}
+
+/**
+ * Checks if a given page is currently locked by anyone for editing.
+ * removes stale lockfiles
+ */
+function checklock($id){
+ global $conf;
+ $lock = wikiFN($id).'.lock';
+
+ //no lockfile
+ if(!@file_exists($lock)) return false;
+
+ //lockfile expired
+ if((time() - filemtime($lock)) > $conf['locktime']){
+ unlink($lock);
+ return false;
+ }
+
+ //my own lock
+ $ip = io_readFile($lock);
+ if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
+ return false;
+ }
+
+ return $ip;
+}
+
+/**
+ * Locks a page for editing
+ */
+function lock($id){
+ $lock = wikiFN($id).'.lock';
+ if($_SERVER['REMOTE_USER']){
+ io_saveFile($lock,$_SERVER['REMOTE_USER']);
+ }else{
+ io_saveFile($lock,clientIP());
+ }
+}
+
+/**
+ * Unlocks a page if it was locked by the user
+ *
+ * return true if a lock was removed
+ */
+function unlock($id){
+ $lock = wikiFN($id).'.lock';
+ if(@file_exists($lock)){
+ $ip = io_readFile($lock);
+ if( ($ip == clientIP()) || ($ip == $_SERVER['REMOTE_USER']) ){
+ @unlink($lock);
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Cleans a given ID to only use allowed characters. Accented characters are
+ * converted to unaccented ones
+ */
+function cleanID($id){
+ global $conf;
+ global $lang;
+ $id = trim($id);
+ $id = strtolower($id);
+
+ //alternative namespace seperator
+ $id = strtr($id,';',':');
+ $id = strtr($id,'/',':');
+
+ if(!$conf['localnames']){
+ if($lang['encoding'] == 'iso-8859-15'){
+ // replace accented chars with unaccented ones
+ // this may look strange on your terminal - just don't touch
+ $id = strtr(
+ strtr($id,
+ 'ŠŽšžŸÀÁÂÃÅÇÈÉÊËÌÍÎÏÑÒÓÔÕØÙÚÛÝàáâãåçèéêëìíîïñòóôõøùúûýÿ',
+ 'szszyaaaaaceeeeiiiinooooouuuyaaaaaceeeeiiiinooooouuuyy'),
+ array('Þ' => 'th', 'þ' => 'th', 'Ð' => 'dh', 'ð' => 'dh', 'ß' => 'ss',
+ 'Œ' => 'oe', 'œ' => 'oe', 'Æ' => 'ae', 'æ' => 'ae', 'µ' => 'u',
+ 'ü' => 'ue', 'ö' => 'oe', 'ä' => 'ae', 'Ü' => 'ue', 'Ö' => 'ö',
+ 'Ä' => 'ae'));
+ }
+ $WORD = 'a-z';
+ }else{
+ $WORD = '\w';
+ }
+
+ //special chars left will be converted to _
+ $id = preg_replace('#[^'.$WORD.'0-9:\-\.]#','_',$id);
+ $id = preg_replace('#__#','_',$id);
+ $id = preg_replace('#:+#',':',$id);
+ $id = trim($id,':._-');
+ $id = preg_replace('#:[:\._\-]+#',':',$id);
+
+ return($id);
+}
+
+/**
+ * returns the full path to the datafile specified by ID and
+ * optional revision
+ */
+function wikiFN($id,$rev=''){
+ global $conf;
+ $id = cleanID($id);
+ $id = str_replace(':','/',$id);
+ if(empty($rev)){
+ return $conf['datadir'].'/'.$id.'.txt';
+ }else{
+ $fn = $conf['olddir'].'/'.$id.'.'.$rev.'.txt';
+ if(!$conf['usegzip'] || @file_exists($fn)){
+ //return plaintext if exists or gzip is disabled
+ return $fn;
+ }else{
+ return $fn.'.gz';
+ }
+ }
+}
+
+/**
+ * Returns the full filepath to a localized textfile if local
+ * version isn't found the english one is returned
+ */
+function localeFN($id){
+ global $conf;
+ $file = './lang/'.$conf['lang'].'/'.$id.'.txt';
+ if(!@file_exists($file)){
+ //fall back to english
+ $file = './lang/en/'.$id.'.txt';
+ }
+ return cleanText($file);
+}
+
+/**
+ * convert line ending to unix format
+ *
+ * @see: formText() for 2crlf conversion
+ */
+function cleanText($text){
+ $text = preg_replace("/(\015\012)|(\015)/","\012",$text);
+ return $text;
+}
+
+/**
+ * Prepares text for print in Webforms by encoding special chars.
+ * It also converts line endings to Windows format which is
+ * pseudo standard for webforms.
+ *
+ * @see: cleanText() for 2unix conversion
+ */
+function formText($text){
+ $text = preg_replace("/\012/","\015\012",$text);
+ return htmlspecialchars($text);
+}
+
+/**
+ * Returns the specified textfile in parsed format
+ */
+function parsedLocale($id){
+ //disable section editing
+ global $parser;
+ $se = $parser['secedit'];
+ $parser['secedit'] = false;
+ //fetch parsed locale
+ $html = io_cacheParse(localeFN($id));
+ //reset section editing
+ $parser['secedit'] = $se;
+ return $html;
+}
+
+/**
+ * Returns the specified textfile in parsed format
+ */
+function rawLocale($id){
+ return io_readFile(localeFN($id));
+}
+
+
+/**
+ * Returns the parsed Wikitext for the given id and revision. If $excuse
+ * is true an explanation is returned if the file wasn't found
+ */
+function parsedWiki($id,$rev='',$excuse=true){
+ $file = wikiFN($id,$rev);
+ $ret = '';
+
+ //ensure $id is in global $ID (needed for parsing)
+ global $ID;
+ $ID = $id;
+
+ if($rev){
+ if(@file_exists($file)){
+ $ret = parse(io_readFile($file));
+ }elseif($excuse){
+ $ret = parsedLocale('norev');
+ }
+ }else{
+ if(@file_exists($file)){
+ $ret = io_cacheParse($file);
+ }elseif($excuse){
+ $ret = parsedLocale('newpage');
+ }
+ }
+ return $ret;
+}
+
+/**
+ * Returns the raw WikiText
+ */
+function rawWiki($id,$rev=''){
+ return io_readFile(wikiFN($id,$rev));
+}
+
+/**
+ * Returns the raw Wiki Text in three slices. The range parameter
+ * Need to have the form "from-to" and gives the range of the section.
+ * The returned order is prefix, section and suffix.
+ */
+function rawWikiSlices($range,$id,$rev=''){
+ list($from,$to) = split('-',$range,2);
+ $text = io_readFile(wikiFN($id,$rev));
+ $text = split("\n",$text);
+ if(!$from) $from = 0;
+ if(!$to) $to = count($text);
+
+ $slices[0] = join("\n",array_slice($text,0,$from));
+ $slices[1] = join("\n",array_slice($text,$from,$to + 1 - $from));
+ $slices[2] = join("\n",array_slice($text,$to+1));
+
+ return $slices;
+}
+
+/**
+ * function to join the text slices with correct lineendings again.
+ * When the pretty parameter is set to true it adds additional empty
+ * lines between sections if needed (used on saving).
+ */
+function con($pre,$text,$suf,$pretty=false){
+
+ if($pretty){
+ if($pre && substr($pre,-1) != "\n") $pre .= "\n";
+ if($suf && substr($text,-1) != "\n") $text .= "\n";
+ }
+
+ if($pre) $pre .= "\n";
+ if($suf) $text .= "\n";
+ return $pre.$text.$suf;
+}
+
+/**
+ * little function to print the content of a var
+ */
+function dbg($msg,$hidden=false){
+ (!$hidden) ? print '
' : print "";
+}
+
+/**
+ * Add's an entry to the changelog
+ */
+function addLogEntry($id,$summary=""){
+ global $conf;
+ $id = cleanID($id);
+ $date = time();
+ $remote = $_SERVER['REMOTE_ADDR'];
+ $user = $_SERVER['REMOTE_USER'];
+
+ $logline = join("\t",array($date,$remote,$id,$user,$summary))."\n";
+
+ $fh = fopen($conf['changelog'],'a');
+ if($fh){
+ fwrite($fh,$logline);
+ fclose($fh);
+ }
+}
+
+/**
+ * returns an array of recently changed files using the
+ * changelog
+ */
+function getRecents($num=0,$incdel=false){
+ global $conf;
+ $recent = array();
+ if(!$num) $num = $conf['recent'];
+
+ $loglines = file($conf['changelog']);
+ rsort($loglines); //reverse sort on timestamp
+
+ foreach ($loglines as $line){
+ $line = rtrim($line); //remove newline
+ if(empty($line)) continue; //skip empty lines
+ $info = split("\t",$line); //split into parts
+ //add id if not in yet and file still exists and is allowed to read
+ if(!$recent[$info[2]] &&
+ (@file_exists(wikiFN($info[2])) || $incdel) &&
+ (auth_quickaclcheck($info[2]) >= AUTH_READ)
+ ){
+ $recent[$info[2]]['date'] = $info[0];
+ $recent[$info[2]]['ip'] = $info[1];
+ $recent[$info[2]]['user'] = $info[3];
+ $recent[$info[2]]['sum'] = $info[4];
+ $recent[$info[2]]['del'] = !@file_exists(wikiFN($info[2]));
+ }
+ if(count($recent) >= $num){
+ break; //finish if enough items found
+ }
+ }
+ return $recent;
+}
+
+/**
+ * Saves a wikitext by calling io_saveFile
+ */
+function saveWikiText($id,$text,$summary){
+ global $conf;
+ global $lang;
+ umask($conf['umask']);
+ // ignore if no changes were made
+ if($text == rawWiki($id,'')){
+ return;
+ }
+
+ $file = wikiFN($id);
+ $old = saveOldRevision($id);
+
+ if (empty($text)){
+ // remove empty files
+ @unlink($file);
+ $del = true;
+ $summary = $lang['deleted']; //autoset summary on deletion
+ }else{
+ // save file (datadir is created in io_saveFile)
+ io_saveFile($file,$text);
+ $del = false;
+ }
+
+ addLogEntry($id,$summary);
+ notify($id,$old,$summary);
+
+ //purge cache on add by updating the purgefile
+ if($conf['purgeonadd'] && (!$old || $del)){
+ io_saveFile($conf['datadir'].'/.cache/purgefile',time());
+ }
+}
+
+/**
+ * moves the current version to the attic and returns its
+ * revision date
+ */
+function saveOldRevision($id){
+ global $conf;
+ umask($conf['umask']);
+ $oldf = wikiFN($id);
+ if(!@file_exists($oldf)) return '';
+ $date = filemtime($oldf);
+ $newf = wikiFN($id,$date);
+ if(substr($newf,-3)=='.gz'){
+ io_saveFile($newf,rawWiki($id));
+ }else{
+ io_makeFileDir($newf);
+ copy($oldf, $newf);
+ }
+ return $date;
+}
+
+/**
+ * Sends a notify mail to the wikiadmin when a page was
+ * changed
+ */
+function notify($id,$rev="",$summary=""){
+ global $lang;
+ global $conf;
+ $hdrs ='';
+ if(empty($conf['notify'])) return; //notify enabled?
+
+ $text = rawLocale('mailtext');
+ $text = str_replace('@DATE@',date($conf['dformat']),$text);
+ $text = str_replace('@BROWSER@',$_SERVER['HTTP_USER_AGENT'],$text);
+ $text = str_replace('@IPADDRESS@',$_SERVER['REMOTE_ADDR'],$text);
+ $text = str_replace('@HOSTNAME@',gethostbyaddr($_SERVER['REMOTE_ADDR']),$text);
+ $text = str_replace('@NEWPAGE@',wl($id,'','',true),$text);
+ $text = str_replace('@DOKUWIKIURL@',getBaseURL(true),$text);
+ $text = str_replace('@SUMMARY@',$summary,$text);
+
+ if($rev){
+ $subject = $lang['mail_changed'].' '.$id;
+ $text = str_replace('@OLDPAGE@',wl($id,"rev=$rev",'',true),$text);
+ require_once("inc/DifferenceEngine.php");
+ $df = new Diff(split("\n",rawWiki($id,$rev)),
+ split("\n",rawWiki($id)));
+ $dformat = new UnifiedDiffFormatter();
+ $diff = $dformat->format($df);
+ }else{
+ $subject=$lang['mail_newpage'].' '.$id;
+ $text = str_replace('@OLDPAGE@','none',$text);
+ $diff = rawWiki($id);
+ }
+ $text = str_replace('@DIFF@',$diff,$text);
+
+ if (!empty($conf['mailfrom'])) {
+ $hdrs = 'From: '.$conf['mailfrom']."\n";
+ }
+ @mail($conf['notify'],$subject,$text,$hdrs);
+}
+
+function getRevisions($id){
+ $revd = dirname(wikiFN($id,'foo'));
+ $revs = array();
+ $clid = cleanID($id);
+ if(strrpos($clid,':')) $clid = substr($clid,strrpos($clid,':')+1); //remove path
+
+ if (is_dir($revd) && $dh = opendir($revd)) {
+ while (($file = readdir($dh)) !== false) {
+ if (is_dir($revd.'/'.$file)) continue;
+ if (preg_match('/^'.$clid.'\.(\d+)\.txt(\.gz)?$/',$file,$match)){
+ $revs[]=$match[1];
+ }
+ }
+ closedir($dh);
+ }
+ rsort($revs);
+ return $revs;
+}
+
+/**
+ * downloads a file from the net and saves it to the given location
+ */
+function download($url,$file){
+ $fp = @fopen($url,"rb");
+ if(!$fp) return false;
+
+ while(!feof($fp)){
+ $cont.= fread($fp,1024);
+ }
+ fclose($fp);
+
+ $fp2 = @fopen($file,"w");
+ if(!$fp2) return false;
+ fwrite($fp2,$cont);
+ fclose($fp2);
+ return true;
+}
+
+/**
+ * extracts the query from a google referer
+ */
+function getGoogleQuery(){
+ $url = parse_url($_SERVER['HTTP_REFERER']);
+
+ if(!preg_match("#google\.#i",$url['host'])) return '';
+ $query = array();
+ parse_str($url['query'],$query);
+
+ return $query['q'];
+}
+
+/**
+ * This function tries the locales given in the
+ * language file
+ */
+function setCorrectLocale(){
+ global $conf;
+ global $lang;
+
+ $enc = strtoupper($lang['encoding']);
+ foreach ($lang['locales'] as $loc){
+ //try locale
+ if(@setlocale(LC_ALL,$loc)) return;
+ //try loceale with encoding
+ if(@setlocale(LC_ALL,"$loc.$enc")) return;
+ }
+ //still here? try to set from environment
+ @setlocale(LC_ALL,"");
+}
+
+/**
+* Return the human readable size of a file
+*
+* @param int $size A file size
+* @param int $dec A number of decimal places
+* @author Martin Benjamin
+* @author Aidan Lister
+* @version 1.0.0
+*/
+function filesize_h($size, $dec = 1)
+{
+ $sizes = array('B', 'KB', 'MB', 'GB');
+ $count = count($sizes);
+ $i = 0;
+
+ while ($size >= 1024 && ($i < $count - 1)) {
+ $size /= 1024;
+ $i++;
+ }
+
+ return round($size, $dec) . ' ' . $sizes[$i];
+}
+
+function check(){
+ global $conf;
+ global $INFO;
+
+ if(is_writable($conf['changelog'])){
+ msg('Changelog is writable',1);
+ }else{
+ msg('Changelog is not writable',-1);
+ }
+
+ if(is_writable($conf['datadir'])){
+ msg('Datadir is writable',1);
+ }else{
+ msg('Datadir is not writable',-1);
+ }
+
+ if(is_writable($conf['olddir'])){
+ msg('Attic is writable',1);
+ }else{
+ msg('Attic is not writable',-1);
+ }
+
+ if(is_writable($conf['mediadir'])){
+ msg('Mediadir is writable',1);
+ }else{
+ msg('Mediadir is not writable',-1);
+ }
+
+ if(is_writable('conf/users.auth')){
+ msg('conf/users.auth is writable',1);
+ }else{
+ msg('conf/users.auth is not writable',0);
+ }
+
+ msg('Your current permission for this page is '.$INFO['perm'],0);
+
+ if(is_writable($INFO['filepath'])){
+ msg('The current page is writable by the webserver',0);
+ }else{
+ msg('The current page is not writable by the webserver',0);
+ }
+
+ if($INFO['writable']){
+ msg('The current page is writable by you',0);
+ }else{
+ msg('The current page is not writable you',0);
+ }
+}
+?>
diff --git a/inc/feedcreator.class.php b/inc/feedcreator.class.php
new file mode 100644
index 000000000..dbd1edc95
--- /dev/null
+++ b/inc/feedcreator.class.php
@@ -0,0 +1,1548 @@
+
+
+****************************************************************************
+
+
+Changelog:
+
+v1.7.1
+ fixed a syntax bug
+ fixed left over debug code
+
+v1.7
+ added HTML and JavaScript feeds (configurable via CSS) (thanks to Pascal Van Hecke)
+ added HTML descriptions for all feed formats (thanks to Pascal Van Hecke)
+ added a switch to select an external stylesheet (thanks to Pascal Van Hecke)
+ changed default content-type to application/xml
+ added character encoding setting
+ fixed numerous smaller bugs (thanks to Sören Fuhrmann of golem.de)
+ improved changing ATOM versions handling (thanks to August Trometer)
+ improved the UniversalFeedCreator's useCached method (thanks to Sören Fuhrmann of golem.de)
+ added charset output in HTTP headers (thanks to Sören Fuhrmann of golem.de)
+ added Slashdot namespace to RSS 1.0 (thanks to Sören Fuhrmann of golem.de)
+
+v1.6 05-10-04
+ added stylesheet to RSS 1.0 feeds
+ fixed generator comment (thanks Kevin L. Papendick and Tanguy Pruvot)
+ fixed RFC822 date bug (thanks Tanguy Pruvot)
+ added TimeZone customization for RFC8601 (thanks Tanguy Pruvot)
+ fixed Content-type could be empty (thanks Tanguy Pruvot)
+ fixed author/creator in RSS1.0 (thanks Tanguy Pruvot)
+
+v1.6 beta 02-28-04
+ added Atom 0.3 support (not all features, though)
+ improved OPML 1.0 support (hopefully - added more elements)
+ added support for arbitrary additional elements (use with caution)
+ code beautification :-)
+ considered beta due to some internal changes
+
+v1.5.1 01-27-04
+ fixed some RSS 1.0 glitches (thanks to Stéphane Vanpoperynghe)
+ fixed some inconsistencies between documentation and code (thanks to Timothy Martin)
+
+v1.5 01-06-04
+ added support for OPML 1.0
+ added more documentation
+
+v1.4 11-11-03
+ optional feed saving and caching
+ improved documentation
+ minor improvements
+
+v1.3 10-02-03
+ renamed to FeedCreator, as it not only creates RSS anymore
+ added support for mbox
+ tentative support for echo/necho/atom/pie/???
+
+v1.2 07-20-03
+ intelligent auto-truncating of RSS 0.91 attributes
+ don't create some attributes when they're not set
+ documentation improved
+ fixed a real and a possible bug with date conversions
+ code cleanup
+
+v1.1 06-29-03
+ added images to feeds
+ now includes most RSS 0.91 attributes
+ added RSS 2.0 feeds
+
+v1.0 06-24-03
+ initial release
+
+
+
+***************************************************************************/
+
+/*** GENERAL USAGE *********************************************************
+
+include("feedcreator.class.php");
+
+$rss = new UniversalFeedCreator();
+$rss->useCached(); // use cached version if age<1 hour
+$rss->title = "PHP news";
+$rss->description = "daily news from the PHP scripting world";
+
+//optional
+$rss->descriptionTruncSize = 500;
+$rss->descriptionHtmlSyndicated = true;
+
+$rss->link = "http://www.dailyphp.net/news";
+$rss->syndicationURL = "http://www.dailyphp.net/".$_SERVER["PHP_SELF"];
+
+$image = new FeedImage();
+$image->title = "dailyphp.net logo";
+$image->url = "http://www.dailyphp.net/images/logo.gif";
+$image->link = "http://www.dailyphp.net";
+$image->description = "Feed provided by dailyphp.net. Click to visit.";
+
+//optional
+$image->descriptionTruncSize = 500;
+$image->descriptionHtmlSyndicated = true;
+
+$rss->image = $image;
+
+// get your news items from somewhere, e.g. your database:
+mysql_select_db($dbHost, $dbUser, $dbPass);
+$res = mysql_query("SELECT * FROM news ORDER BY newsdate DESC");
+while ($data = mysql_fetch_object($res)) {
+ $item = new FeedItem();
+ $item->title = $data->title;
+ $item->link = $data->url;
+ $item->description = $data->short;
+
+ //optional
+ item->descriptionTruncSize = 500;
+ item->descriptionHtmlSyndicated = true;
+
+ $item->date = $data->newsdate;
+ $item->source = "http://www.dailyphp.net";
+ $item->author = "John Doe";
+
+ $rss->addItem($item);
+}
+
+// valid format strings are: RSS0.91, RSS1.0, RSS2.0, PIE0.1 (deprecated),
+// MBOX, OPML, ATOM, ATOM0.3, HTML, JS
+echo $rss->saveFeed("RSS1.0", "news/feed.xml");
+
+
+***************************************************************************
+* A little setup *
+**************************************************************************/
+
+// your local timezone, set to "" to disable or for GMT
+define("TIME_ZONE","+01:00");
+
+
+
+
+/**
+ * Version string.
+ **/
+define("FEEDCREATOR_VERSION", "FeedCreator 1.7.1");
+
+
+
+/**
+ * A FeedItem is a part of a FeedCreator feed.
+ *
+ * @author Kai Blankenhorn
+ * @since 1.3
+ */
+class FeedItem extends HtmlDescribable {
+ /**
+ * Mandatory attributes of an item.
+ */
+ var $title, $description, $link;
+
+ /**
+ * Optional attributes of an item.
+ */
+ var $author, $authorEmail, $image, $category, $comments, $guid, $source, $creator;
+
+ /**
+ * Publishing date of an item. May be in one of the following formats:
+ *
+ * RFC 822:
+ * "Mon, 20 Jan 03 18:05:41 +0400"
+ * "20 Jan 03 18:05:41 +0000"
+ *
+ * ISO 8601:
+ * "2003-01-20T18:05:41+04:00"
+ *
+ * Unix:
+ * 1043082341
+ */
+ var $date;
+
+ /**
+ * Any additional elements to include as an assiciated array. All $key => $value pairs
+ * will be included unencoded in the feed item in the form
+ * <$key>$value$key>
+ * Again: No encoding will be used! This means you can invalidate or enhance the feed
+ * if $value contains markup. This may be abused to embed tags not implemented by
+ * the FeedCreator class used.
+ */
+ var $additionalElements = Array();
+
+ // on hold
+ // var $source;
+}
+
+
+
+/**
+ * An FeedImage may be added to a FeedCreator feed.
+ * @author Kai Blankenhorn
+ * @since 1.3
+ */
+class FeedImage extends HtmlDescribable {
+ /**
+ * Mandatory attributes of an image.
+ */
+ var $title, $url, $link;
+
+ /**
+ * Optional attributes of an image.
+ */
+ var $width, $height, $description;
+}
+
+
+
+/**
+ * An HtmlDescribable is an item within a feed that can have a description that may
+ * include HTML markup.
+ */
+class HtmlDescribable {
+ /**
+ * Indicates whether the description field should be rendered in HTML.
+ */
+ var $descriptionHtmlSyndicated;
+
+ /**
+ * Indicates whether and to how many characters a description should be truncated.
+ */
+ var $descriptionTruncSize;
+
+ /**
+ * Returns a formatted description field, depending on descriptionHtmlSyndicated and
+ * $descriptionTruncSize properties
+ * @return string the formatted description
+ */
+ function getDescription() {
+ $descriptionField = new FeedHtmlField($this->description);
+ $descriptionField->syndicateHtml = $this->descriptionHtmlSyndicated;
+ $descriptionField->truncSize = $this->descriptionTruncSize;
+ return $descriptionField->output();
+ }
+
+}
+
+
+/**
+ * An FeedHtmlField describes and generates
+ * a feed, item or image html field (probably a description). Output is
+ * generated based on $truncSize, $syndicateHtml properties.
+ * @author Pascal Van Hecke
+ * @version 1.6
+ */
+class FeedHtmlField {
+ /**
+ * Mandatory attributes of a FeedHtmlField.
+ */
+ var $rawFieldContent;
+
+ /**
+ * Optional attributes of a FeedHtmlField.
+ *
+ */
+ var $truncSize, $syndicateHtml;
+
+ /**
+ * Creates a new instance of FeedHtmlField.
+ * @param $string: if given, sets the rawFieldContent property
+ */
+ function FeedHtmlField($parFieldContent) {
+ if ($parFieldContent) {
+ $this->rawFieldContent = $parFieldContent;
+ }
+ }
+
+
+ /**
+ * Creates the right output, depending on $truncSize, $syndicateHtml properties.
+ * @return string the formatted field
+ */
+ function output() {
+ // when field available and syndicated in html we assume
+ // - valid html in $rawFieldContent and we enclose in CDATA tags
+ // - no truncation (truncating risks producing invalid html)
+ if (!$this->rawFieldContent) {
+ $result = "";
+ } elseif ($this->syndicateHtml) {
+ $result = "rawFieldContent."]]>";
+ } else {
+ if ($this->truncSize and is_int($this->truncSize)) {
+ $result = FeedCreator::iTrunc(htmlspecialchars($this->rawFieldContent),$this->truncSize);
+ } else {
+ $result = htmlspecialchars($this->rawFieldContent);
+ }
+ }
+ return $result;
+ }
+
+}
+
+
+
+/**
+ * UniversalFeedCreator lets you choose during runtime which
+ * format to build.
+ * For general usage of a feed class, see the FeedCreator class
+ * below or the example above.
+ *
+ * @since 1.3
+ * @author Kai Blankenhorn
+ */
+class UniversalFeedCreator extends FeedCreator {
+ var $_feed;
+
+ function _setFormat($format) {
+ switch (strtoupper($format)) {
+
+ case "2.0":
+ // fall through
+ case "RSS2.0":
+ $this->_feed = new RSSCreator20();
+ break;
+
+ case "1.0":
+ // fall through
+ case "RSS1.0":
+ $this->_feed = new RSSCreator10();
+ break;
+
+ case "0.91":
+ // fall through
+ case "RSS0.91":
+ $this->_feed = new RSSCreator091();
+ break;
+
+ case "PIE0.1":
+ $this->_feed = new PIECreator01();
+ break;
+
+ case "MBOX":
+ $this->_feed = new MBOXCreator();
+ break;
+
+ case "OPML":
+ $this->_feed = new OPMLCreator();
+ break;
+
+ case "ATOM":
+ // fall through: always the latest ATOM version
+
+ case "ATOM0.3":
+ $this->_feed = new AtomCreator03();
+ break;
+
+ case "HTML":
+ $this->_feed = new HTMLCreator();
+ break;
+
+ case "JS":
+ // fall through
+ case "JAVASCRIPT":
+ $this->_feed = new JSCreator();
+ break;
+
+ default:
+ $this->_feed = new RSSCreator091();
+ break;
+ }
+
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ // prevent overwriting of properties "contentType", "encoding"; do not copy "_feed" itself
+ if (!in_array($key, array("_feed", "contentType", "encoding"))) {
+ $this->_feed->{$key} = $this->{$key};
+ }
+ }
+ }
+
+ /**
+ * Creates a syndication feed based on the items previously added.
+ *
+ * @see FeedCreator::addItem()
+ * @param string format format the feed should comply to. Valid values are:
+ * "PIE0.1", "mbox", "RSS0.91", "RSS1.0", "RSS2.0", "OPML", "ATOM0.3", "HTML", "JS"
+ * @return string the contents of the feed.
+ */
+ function createFeed($format = "RSS0.91") {
+ $this->_setFormat($format);
+ return $this->_feed->createFeed();
+ }
+
+
+
+ /**
+ * Saves this feed as a file on the local disk. After the file is saved, an HTTP redirect
+ * header may be sent to redirect the use to the newly created file.
+ * @since 1.4
+ *
+ * @param string format format the feed should comply to. Valid values are:
+ * "PIE0.1" (deprecated), "mbox", "RSS0.91", "RSS1.0", "RSS2.0", "OPML", "ATOM", "ATOM0.3", "HTML", "JS"
+ * @param string filename optional the filename where a recent version of the feed is saved. If not specified, the filename is $_SERVER["PHP_SELF"] with the extension changed to .xml (see _generateFilename()).
+ * @param boolean displayContents optional send the content of the file or not. If true, the file will be sent in the body of the response.
+ */
+ function saveFeed($format="RSS0.91", $filename="", $displayContents=true) {
+ $this->_setFormat($format);
+ $this->_feed->saveFeed($filename, $displayContents);
+ }
+
+
+ /**
+ * Turns on caching and checks if there is a recent version of this feed in the cache.
+ * If there is, an HTTP redirect header is sent.
+ * To effectively use caching, you should create the FeedCreator object and call this method
+ * before anything else, especially before you do the time consuming task to build the feed
+ * (web fetching, for example).
+ *
+ * @param string format format the feed should comply to. Valid values are:
+ * "PIE0.1" (deprecated), "mbox", "RSS0.91", "RSS1.0", "RSS2.0", "OPML", "ATOM0.3".
+ * @param filename string optional the filename where a recent version of the feed is saved. If not specified, the filename is $_SERVER["PHP_SELF"] with the extension changed to .xml (see _generateFilename()).
+ * @param timeout int optional the timeout in seconds before a cached version is refreshed (defaults to 3600 = 1 hour)
+ */
+ function useCached($format="RSS0.91", $filename="", $timeout=3600) {
+ $this->_setFormat($format);
+ $this->_feed->useCached($filename, $timeout);
+ }
+
+}
+
+
+/**
+ * FeedCreator is the abstract base implementation for concrete
+ * implementations that implement a specific format of syndication.
+ *
+ * @abstract
+ * @author Kai Blankenhorn
+ * @since 1.4
+ */
+class FeedCreator extends HtmlDescribable {
+
+ /**
+ * Mandatory attributes of a feed.
+ */
+ var $title, $description, $link;
+
+
+ /**
+ * Optional attributes of a feed.
+ */
+ var $syndicationURL, $image, $language, $copyright, $pubDate, $lastBuildDate, $editor, $editorEmail, $webmaster, $category, $docs, $ttl, $rating, $skipHours, $skipDays;
+
+ /**
+ * The url of the external xsl stylesheet used to format the naked rss feed.
+ * Ignored in the output when empty.
+ */
+ var $xslStyleSheet = "";
+
+
+ /**
+ * @access private
+ */
+ var $items = Array();
+
+
+ /**
+ * This feed's MIME content type.
+ * @since 1.4
+ * @access private
+ */
+ var $contentType = "application/xml";
+
+
+ /**
+ * This feed's character encoding.
+ * @since 1.6.1
+ **/
+ var $encoding = "ISO-8859-1";
+
+
+ /**
+ * Any additional elements to include as an assiciated array. All $key => $value pairs
+ * will be included unencoded in the feed in the form
+ * <$key>$value$key>
+ * Again: No encoding will be used! This means you can invalidate or enhance the feed
+ * if $value contains markup. This may be abused to embed tags not implemented by
+ * the FeedCreator class used.
+ */
+ var $additionalElements = Array();
+
+
+ /**
+ * Adds an FeedItem to the feed.
+ *
+ * @param object FeedItem $item The FeedItem to add to the feed.
+ * @access public
+ */
+ function addItem($item) {
+ $this->items[] = $item;
+ }
+
+
+ /**
+ * Truncates a string to a certain length at the most sensible point.
+ * First, if there's a '.' character near the end of the string, the string is truncated after this character.
+ * If there is no '.', the string is truncated after the last ' ' character.
+ * If the string is truncated, " ..." is appended.
+ * If the string is already shorter than $length, it is returned unchanged.
+ *
+ * @static
+ * @param string string A string to be truncated.
+ * @param int length the maximum length the string should be truncated to
+ * @return string the truncated string
+ */
+ function iTrunc($string, $length) {
+ if (strlen($string)<=$length) {
+ return $string;
+ }
+
+ $pos = strrpos($string,".");
+ if ($pos>=$length-4) {
+ $string = substr($string,0,$length-4);
+ $pos = strrpos($string,".");
+ }
+ if ($pos>=$length*0.4) {
+ return substr($string,0,$pos+1)." ...";
+ }
+
+ $pos = strrpos($string," ");
+ if ($pos>=$length-4) {
+ $string = substr($string,0,$length-4);
+ $pos = strrpos($string," ");
+ }
+ if ($pos>=$length*0.4) {
+ return substr($string,0,$pos)." ...";
+ }
+
+ return substr($string,0,$length-4)." ...";
+
+ }
+
+
+ /**
+ * Creates a comment indicating the generator of this feed.
+ * The format of this comment seems to be recognized by
+ * Syndic8.com.
+ */
+ function _createGeneratorComment() {
+ return "\n";
+ }
+
+
+ /**
+ * Creates a string containing all additional elements specified in
+ * $additionalElements.
+ * @param elements array an associative array containing key => value pairs
+ * @param indentString string a string that will be inserted before every generated line
+ * @return string the XML tags corresponding to $additionalElements
+ */
+ function _createAdditionalElements($elements, $indentString="") {
+ $ae = "";
+ if (is_array($elements)) {
+ foreach($elements AS $key => $value) {
+ $ae.= $indentString."<$key>$value$key>\n";
+ }
+ }
+ return $ae;
+ }
+
+ function _createStylesheetReferences() {
+ $xml = "";
+ if ($this->cssStyleSheet) $xml .= "cssStyleSheet."\" type=\"text/css\"?>\n";
+ if ($this->xslStyleSheet) $xml .= "xslStyleSheet."\" type=\"text/xsl\"?>\n";
+ return $xml;
+ }
+
+
+ /**
+ * Builds the feed's text.
+ * @abstract
+ * @return string the feed's complete text
+ */
+ function createFeed() {
+ }
+
+ /**
+ * Generate a filename for the feed cache file. The result will be $_SERVER["PHP_SELF"] with the extension changed to .xml.
+ * For example:
+ *
+ * echo $_SERVER["PHP_SELF"]."\n";
+ * echo FeedCreator::_generateFilename();
+ *
+ * would produce:
+ *
+ * /rss/latestnews.php
+ * latestnews.xml
+ *
+ * @return string the feed cache filename
+ * @since 1.4
+ * @access private
+ */
+ function _generateFilename() {
+ $fileInfo = pathinfo($_SERVER["PHP_SELF"]);
+ return substr($fileInfo["basename"],0,-(strlen($fileInfo["extension"])+1)).".xml";
+ }
+
+
+ /**
+ * @since 1.4
+ * @access private
+ */
+ function _redirect($filename) {
+ // attention, heavily-commented-out-area
+
+ // maybe use this in addition to file time checking
+ //Header("Expires: ".date("r",time()+$this->_timeout));
+
+ /* no caching at all, doesn't seem to work as good:
+ Header("Cache-Control: no-cache");
+ Header("Pragma: no-cache");
+ */
+
+ // HTTP redirect, some feed readers' simple HTTP implementations don't follow it
+ //Header("Location: ".$filename);
+
+ Header("Content-Type: ".$this->contentType."; charset=".$this->encoding."; filename=".basename($filename));
+ Header("Content-Disposition: inline; filename=".basename($filename));
+ readfile($filename, "r");
+ die();
+ }
+
+ /**
+ * Turns on caching and checks if there is a recent version of this feed in the cache.
+ * If there is, an HTTP redirect header is sent.
+ * To effectively use caching, you should create the FeedCreator object and call this method
+ * before anything else, especially before you do the time consuming task to build the feed
+ * (web fetching, for example).
+ * @since 1.4
+ * @param filename string optional the filename where a recent version of the feed is saved. If not specified, the filename is $_SERVER["PHP_SELF"] with the extension changed to .xml (see _generateFilename()).
+ * @param timeout int optional the timeout in seconds before a cached version is refreshed (defaults to 3600 = 1 hour)
+ */
+ function useCached($filename="", $timeout=3600) {
+ $this->_timeout = $timeout;
+ if ($filename=="") {
+ $filename = $this->_generateFilename();
+ }
+ if (@file_exists($filename) AND (time()-filemtime($filename) < $timeout)) {
+ $this->_redirect($filename);
+ }
+ }
+
+
+ /**
+ * Saves this feed as a file on the local disk. After the file is saved, a redirect
+ * header may be sent to redirect the user to the newly created file.
+ * @since 1.4
+ *
+ * @param filename string optional the filename where a recent version of the feed is saved. If not specified, the filename is $_SERVER["PHP_SELF"] with the extension changed to .xml (see _generateFilename()).
+ * @param redirect boolean optional send an HTTP redirect header or not. If true, the user will be automatically redirected to the created file.
+ */
+ function saveFeed($filename="", $displayContents=true) {
+ if ($filename=="") {
+ $filename = $this->_generateFilename();
+ }
+ $feedFile = fopen($filename, "w+");
+ if ($feedFile) {
+ fputs($feedFile,$this->createFeed());
+ fclose($feedFile);
+ if ($displayContents) {
+ $this->_redirect($filename);
+ }
+ } else {
+ echo " Error creating feed file, please check write permissions. ";
+ }
+ }
+
+}
+
+
+/**
+ * FeedDate is an internal class that stores a date for a feed or feed item.
+ * Usually, you won't need to use this.
+ */
+class FeedDate {
+ var $unix;
+
+ /**
+ * Creates a new instance of FeedDate representing a given date.
+ * Accepts RFC 822, ISO 8601 date formats as well as unix time stamps.
+ * @param mixed $dateString optional the date this FeedDate will represent. If not specified, the current date and time is used.
+ */
+ function FeedDate($dateString="") {
+ if ($dateString=="") $dateString = date("r");
+
+ if (is_integer($dateString)) {
+ $this->unix = $dateString;
+ return;
+ }
+ if (preg_match("~(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),\\s+)?(\\d{1,2})\\s+([a-zA-Z]{3})\\s+(\\d{4})\\s+(\\d{2}):(\\d{2}):(\\d{2})\\s+(.*)~",$dateString,$matches)) {
+ $months = Array("Jan"=>1,"Feb"=>2,"Mar"=>3,"Apr"=>4,"May"=>5,"Jun"=>6,"Jul"=>7,"Aug"=>8,"Sep"=>9,"Oct"=>10,"Nov"=>11,"Dec"=>12);
+ $this->unix = mktime($matches[4],$matches[5],$matches[6],$months[$matches[2]],$matches[1],$matches[3]);
+ if (substr($matches[7],0,1)=='+' OR substr($matches[7],0,1)=='-') {
+ $tzOffset = (substr($matches[7],0,3) * 60 + substr($matches[7],-2)) * 60;
+ } else {
+ if (strlen($matches[7])==1) {
+ $oneHour = 3600;
+ $ord = ord($matches[7]);
+ if ($ord < ord("M")) {
+ $tzOffset = (ord("A") - $ord - 1) * $oneHour;
+ } elseif ($ord >= ord("M") AND $matches[7]!="Z") {
+ $tzOffset = ($ord - ord("M")) * $oneHour;
+ } elseif ($matches[7]=="Z") {
+ $tzOffset = 0;
+ }
+ }
+ switch ($matches[7]) {
+ case "UT":
+ case "GMT": $tzOffset = 0;
+ }
+ }
+ $this->unix += $tzOffset;
+ return;
+ }
+ if (preg_match("~(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(.*)~",$dateString,$matches)) {
+ $this->unix = mktime($matches[4],$matches[5],$matches[6],$matches[2],$matches[3],$matches[1]);
+ if (substr($matches[7],0,1)=='+' OR substr($matches[7],0,1)=='-') {
+ $tzOffset = (substr($matches[7],0,3) * 60 + substr($matches[7],-2)) * 60;
+ } else {
+ if ($matches[7]=="Z") {
+ $tzOffset = 0;
+ }
+ }
+ $this->unix += $tzOffset;
+ return;
+ }
+ $this->unix = 0;
+ }
+
+ /**
+ * Gets the date stored in this FeedDate as an RFC 822 date.
+ *
+ * @return a date in RFC 822 format
+ */
+ function rfc822() {
+ //return gmdate("r",$this->unix);
+ $date = gmdate("D, d M Y H:i:s", $this->unix);
+ if (TIME_ZONE!="") $date .= " ".str_replace(":","",TIME_ZONE);
+ return $date;
+ }
+
+ /**
+ * Gets the date stored in this FeedDate as an ISO 8601 date.
+ *
+ * @return a date in ISO 8601 format
+ */
+ function iso8601() {
+ $date = gmdate("Y-m-d\TH:i:sO",$this->unix);
+ $date = substr($date,0,22) . ':' . substr($date,-2);
+ if (TIME_ZONE!="") $date = str_replace("+00:00",TIME_ZONE,$date);
+ return $date;
+ }
+
+ /**
+ * Gets the date stored in this FeedDate as unix time stamp.
+ *
+ * @return a date as a unix time stamp
+ */
+ function unix() {
+ return $this->unix;
+ }
+}
+
+
+/**
+ * RSSCreator10 is a FeedCreator that implements RDF Site Summary (RSS) 1.0.
+ *
+ * @see http://www.purl.org/rss/1.0/
+ * @since 1.3
+ * @author Kai Blankenhorn
+ */
+class RSSCreator10 extends FeedCreator {
+
+ /**
+ * Builds the RSS feed's text. The feed will be compliant to RDF Site Summary (RSS) 1.0.
+ * The feed will contain all items previously added in the same order.
+ * @return string the feed's complete text
+ */
+ function createFeed() {
+ $feed = "encoding."\"?>\n";
+ $feed.= $this->_createGeneratorComment();
+ if ($this->cssStyleSheet=="") {
+ $cssStyleSheet = "http://www.w3.org/2000/08/w3c-synd/style.css";
+ }
+ $feed.= $this->_createStylesheetReferences();
+ $feed.= "\n";
+ $feed.= " syndicationURL."\">\n";
+ $feed.= " ".htmlspecialchars($this->title)."\n";
+ $feed.= " ".htmlspecialchars($this->description)."\n";
+ $feed.= " ".$this->link."\n";
+ if ($this->image!=null) {
+ $feed.= " image->url."\" />\n";
+ }
+ $now = new FeedDate();
+ $feed.= " ".htmlspecialchars($now->iso8601())."\n";
+ $feed.= " \n";
+ $feed.= " \n";
+ for ($i=0;$iitems);$i++) {
+ $feed.= " items[$i]->link)."\"/>\n";
+ }
+ $feed.= " \n";
+ $feed.= " \n";
+ $feed.= " \n";
+ if ($this->image!=null) {
+ $feed.= " image->url."\">\n";
+ $feed.= " ".$this->image->title."\n";
+ $feed.= " ".$this->image->link."\n";
+ $feed.= " ".$this->image->url."\n";
+ $feed.= " \n";
+ }
+ $feed.= $this->_createAdditionalElements($this->additionalElements, " ");
+
+ for ($i=0;$iitems);$i++) {
+ $feed.= " items[$i]->link)."\">\n";
+ //$feed.= " Posting\n";
+ $feed.= " text/html\n";
+ if ($this->items[$i]->date!=null) {
+ $itemDate = new FeedDate($this->items[$i]->date);
+ $feed.= " ".htmlspecialchars($itemDate->iso8601())."\n";
+ }
+ if ($this->items[$i]->source!="") {
+ $feed.= " ".htmlspecialchars($this->items[$i]->source)."\n";
+ }
+ if ($this->items[$i]->author!="") {
+ $feed.= " ".htmlspecialchars($this->items[$i]->author)."\n";
+ }
+ $feed.= " ".htmlspecialchars(strip_tags(strtr($this->items[$i]->title,"\n\r"," ")))."\n";
+ $feed.= " ".htmlspecialchars($this->items[$i]->link)."\n";
+ $feed.= " ".htmlspecialchars($this->items[$i]->description)."\n";
+ $feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " ");
+ $feed.= " \n";
+ }
+ $feed.= "\n";
+ return $feed;
+ }
+}
+
+
+
+/**
+ * RSSCreator091 is a FeedCreator that implements RSS 0.91 Spec, revision 3.
+ *
+ * @see http://my.netscape.com/publish/formats/rss-spec-0.91.html
+ * @since 1.3
+ * @author Kai Blankenhorn
+ */
+class RSSCreator091 extends FeedCreator {
+
+ /**
+ * Stores this RSS feed's version number.
+ * @access private
+ */
+ var $RSSVersion;
+
+ function RSSCreator091() {
+ $this->_setRSSVersion("0.91");
+ $this->contentType = "application/rss+xml";
+ }
+
+ /**
+ * Sets this RSS feed's version number.
+ * @access private
+ */
+ function _setRSSVersion($version) {
+ $this->RSSVersion = $version;
+ }
+
+ /**
+ * Builds the RSS feed's text. The feed will be compliant to RDF Site Summary (RSS) 1.0.
+ * The feed will contain all items previously added in the same order.
+ * @return string the feed's complete text
+ */
+ function createFeed() {
+ $feed = "encoding."\"?>\n";
+ $feed.= $this->_createGeneratorComment();
+ $feed.= $this->_createStylesheetReferences();
+ $feed.= "RSSVersion."\">\n";
+ $feed.= " \n";
+ $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->title),100)."\n";
+ $this->descriptionTruncSize = 500;
+ $feed.= " ".$this->getDescription()."\n";
+ $feed.= " ".$this->link."\n";
+ $now = new FeedDate();
+ $feed.= " ".htmlspecialchars($now->rfc822())."\n";
+ $feed.= " ".FEEDCREATOR_VERSION."\n";
+
+ if ($this->image!=null) {
+ $feed.= " \n";
+ $feed.= " ".$this->image->url."\n";
+ $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->image->title),100)."\n";
+ $feed.= " ".$this->image->link."\n";
+ if ($this->image->width!="") {
+ $feed.= " ".$this->image->width."\n";
+ }
+ if ($this->image->height!="") {
+ $feed.= " ".$this->image->height."\n";
+ }
+ if ($this->image->description!="") {
+ $feed.= " ".$this->image->getDescription()."\n";
+ }
+ $feed.= " \n";
+ }
+ if ($this->language!="") {
+ $feed.= " ".$this->language."\n";
+ }
+ if ($this->copyright!="") {
+ $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->copyright),100)."\n";
+ }
+ if ($this->editor!="") {
+ $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->editor),100)."\n";
+ }
+ if ($this->webmaster!="") {
+ $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->webmaster),100)."\n";
+ }
+ if ($this->pubDate!="") {
+ $pubDate = new FeedDate($this->pubDate);
+ $feed.= " ".htmlspecialchars($pubDate->rfc822())."\n";
+ }
+ if ($this->category!="") {
+ $feed.= " ".htmlspecialchars($this->category)."\n";
+ }
+ if ($this->docs!="") {
+ $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->docs),500)."\n";
+ }
+ if ($this->ttl!="") {
+ $feed.= " ".htmlspecialchars($this->ttl)."\n";
+ }
+ if ($this->rating!="") {
+ $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->rating),500)."\n";
+ }
+ if ($this->skipHours!="") {
+ $feed.= " ".htmlspecialchars($this->skipHours)."\n";
+ }
+ if ($this->skipDays!="") {
+ $feed.= " ".htmlspecialchars($this->skipDays)."\n";
+ }
+ $feed.= $this->_createAdditionalElements($this->additionalElements, " ");
+
+ for ($i=0;$iitems);$i++) {
+ $feed.= " \n";
+ $feed.= " ".FeedCreator::iTrunc(htmlspecialchars(strip_tags($this->items[$i]->title)),100)."\n";
+ $feed.= " ".htmlspecialchars($this->items[$i]->link)."\n";
+ $feed.= " ".$this->items[$i]->getDescription()."\n";
+
+ if ($this->items[$i]->author!="") {
+ $feed.= " ".htmlspecialchars($this->items[$i]->author)."\n";
+ }
+ /*
+ // on hold
+ if ($this->items[$i]->source!="") {
+ $feed.= " ".htmlspecialchars($this->items[$i]->source)."\n";
+ }
+ */
+ if ($this->items[$i]->category!="") {
+ $feed.= " ".htmlspecialchars($this->items[$i]->category)."\n";
+ }
+ if ($this->items[$i]->comments!="") {
+ $feed.= " ".htmlspecialchars($this->items[$i]->comments)."\n";
+ }
+ if ($this->items[$i]->date!="") {
+ $itemDate = new FeedDate($this->items[$i]->date);
+ $feed.= " ".htmlspecialchars($itemDate->rfc822())."\n";
+ }
+ if ($this->items[$i]->guid!="") {
+ $feed.= " ".htmlspecialchars($this->items[$i]->guid)."\n";
+ }
+ $feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " ");
+ $feed.= " \n";
+ }
+ $feed.= " \n";
+ $feed.= "\n";
+ return $feed;
+ }
+}
+
+
+
+/**
+ * RSSCreator20 is a FeedCreator that implements RDF Site Summary (RSS) 2.0.
+ *
+ * @see http://backend.userland.com/rss
+ * @since 1.3
+ * @author Kai Blankenhorn
+ */
+class RSSCreator20 extends RSSCreator091 {
+
+ function RSSCreator20() {
+ parent::_setRSSVersion("2.0");
+ }
+
+}
+
+
+/**
+ * PIECreator01 is a FeedCreator that implements the emerging PIE specification,
+ * as in http://intertwingly.net/wiki/pie/Syntax.
+ *
+ * @deprecated
+ * @since 1.3
+ * @author Scott Reynen and Kai Blankenhorn
+ */
+class PIECreator01 extends FeedCreator {
+
+ function PIECreator01() {
+ $this->encoding = "utf-8";
+ }
+
+ function createFeed() {
+ $feed = "encoding."\"?>\n";
+ $feed.= $this->_createStylesheetReferences();
+ $feed.= "\n";
+ $feed.= " ".FeedCreator::iTrunc(htmlspecialchars($this->title),100)."\n";
+ $this->truncSize = 500;
+ $feed.= " ".$this->getDescription()."\n";
+ $feed.= " ".$this->link."\n";
+ for ($i=0;$iitems);$i++) {
+ $feed.= " \n";
+ $feed.= " ".FeedCreator::iTrunc(htmlspecialchars(strip_tags($this->items[$i]->title)),100)."\n";
+ $feed.= " ".htmlspecialchars($this->items[$i]->link)."\n";
+ $itemDate = new FeedDate($this->items[$i]->date);
+ $feed.= " ".htmlspecialchars($itemDate->iso8601())."\n";
+ $feed.= " ".htmlspecialchars($itemDate->iso8601())."\n";
+ $feed.= " ".htmlspecialchars($itemDate->iso8601())."\n";
+ $feed.= " ".htmlspecialchars($this->items[$i]->guid)."\n";
+ if ($this->items[$i]->author!="") {
+ $feed.= " \n";
+ $feed.= " ".htmlspecialchars($this->items[$i]->author)."\n";
+ if ($this->items[$i]->authorEmail!="") {
+ $feed.= " ".$this->items[$i]->authorEmail."\n";
+ }
+ $feed.=" \n";
+ }
+ $feed.= " \n";
+ $feed.= "
".$this->items[$i]->getDescription()."
\n";
+ $feed.= " \n";
+ $feed.= " \n";
+ }
+ $feed.= "\n";
+ return $feed;
+ }
+}
+
+
+/**
+ * AtomCreator03 is a FeedCreator that implements the atom specification,
+ * as in http://www.intertwingly.net/wiki/pie/FrontPage.
+ * Please note that just by using AtomCreator03 you won't automatically
+ * produce valid atom files. For example, you have to specify either an editor
+ * for the feed or an author for every single feed item.
+ *
+ * Some elements have not been implemented yet. These are (incomplete list):
+ * author URL, item author's email and URL, item contents, alternate links,
+ * other link content types than text/html. Some of them may be created with
+ * AtomCreator03::additionalElements.
+ *
+ * @see FeedCreator#additionalElements
+ * @since 1.6
+ * @author Kai Blankenhorn , Scott Reynen
+ */
+class AtomCreator03 extends FeedCreator {
+
+ function AtomCreator03() {
+ $this->contentType = "application/atom+xml";
+ $this->encoding = "utf-8";
+ }
+
+ function createFeed() {
+ $feed = "encoding."\"?>\n";
+ $feed.= $this->_createGeneratorComment();
+ $feed.= $this->_createStylesheetReferences();
+ $feed.= "language!="") {
+ $feed.= " xml:lang=\"".$this->language."\"";
+ }
+ $feed.= ">\n";
+ $feed.= " ".htmlspecialchars($this->title)."\n";
+ $feed.= " ".htmlspecialchars($this->description)."\n";
+ $feed.= " link)."\"/>\n";
+ $feed.= " ".htmlspecialchars($this->link)."\n";
+ $now = new FeedDate();
+ $feed.= " ".htmlspecialchars($now->iso8601())."\n";
+ if ($this->editor!="") {
+ $feed.= " \n";
+ $feed.= " ".$this->editor."\n";
+ if ($this->editorEmail!="") {
+ $feed.= " ".$this->editorEmail."\n";
+ }
+ $feed.= " \n";
+ }
+ $feed.= " ".FEEDCREATOR_VERSION."\n";
+ $feed.= $this->_createAdditionalElements($this->additionalElements, " ");
+ for ($i=0;$iitems);$i++) {
+ $feed.= " \n";
+ $feed.= " ".htmlspecialchars(strip_tags($this->items[$i]->title))."\n";
+ $feed.= " items[$i]->link)."\"/>\n";
+ if ($this->items[$i]->date=="") {
+ $this->items[$i]->date = time();
+ }
+ $itemDate = new FeedDate($this->items[$i]->date);
+ $feed.= " ".htmlspecialchars($itemDate->iso8601())."\n";
+ $feed.= " ".htmlspecialchars($itemDate->iso8601())."\n";
+ $feed.= " ".htmlspecialchars($itemDate->iso8601())."\n";
+ $feed.= " ".htmlspecialchars($this->items[$i]->link)."\n";
+ $feed.= $this->_createAdditionalElements($this->items[$i]->additionalElements, " ");
+ if ($this->items[$i]->author!="") {
+ $feed.= " \n";
+ $feed.= " ".htmlspecialchars($this->items[$i]->author)."\n";
+ $feed.= " \n";
+ }
+ if ($this->items[$i]->description!="") {
+ $feed.= " ".htmlspecialchars($this->items[$i]->description)."\n";
+ }
+ $feed.= " \n";
+ }
+ $feed.= "\n";
+ return $feed;
+ }
+}
+
+
+/**
+ * MBOXCreator is a FeedCreator that implements the mbox format
+ * as described in http://www.qmail.org/man/man5/mbox.html
+ *
+ * @since 1.3
+ * @author Kai Blankenhorn
+ */
+class MBOXCreator extends FeedCreator {
+
+ function MBOXCreator() {
+ $this->contentType = "text/plain";
+ $this->encoding = "ISO-8859-15";
+ }
+
+ function qp_enc($input = "", $line_max = 76) {
+ $hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
+ $lines = preg_split("/(?:\r\n|\r|\n)/", $input);
+ $eol = "\r\n";
+ $escape = "=";
+ $output = "";
+ while( list(, $line) = each($lines) ) {
+ //$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
+ $linlen = strlen($line);
+ $newline = "";
+ for($i = 0; $i < $linlen; $i++) {
+ $c = substr($line, $i, 1);
+ $dec = ord($c);
+ if ( ($dec == 32) && ($i == ($linlen - 1)) ) { // convert space at eol only
+ $c = "=20";
+ } elseif ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) { // always encode "\t", which is *not* required
+ $h2 = floor($dec/16); $h1 = floor($dec%16);
+ $c = $escape.$hex["$h2"].$hex["$h1"];
+ }
+ if ( (strlen($newline) + strlen($c)) >= $line_max ) { // CRLF is not counted
+ $output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
+ $newline = "";
+ }
+ $newline .= $c;
+ } // end of for
+ $output .= $newline.$eol;
+ }
+ return trim($output);
+ }
+
+
+ /**
+ * Builds the MBOX contents.
+ * @return string the feed's complete text
+ */
+ function createFeed() {
+ for ($i=0;$iitems);$i++) {
+ if ($this->items[$i]->author!="") {
+ $from = $this->items[$i]->author;
+ } else {
+ $from = $this->title;
+ }
+ $itemDate = new FeedDate($this->items[$i]->date);
+ $feed.= "From ".strtr(MBOXCreator::qp_enc($from)," ","_")." ".date("D M d H:i:s Y",$itemDate->unix())."\n";
+ $feed.= "Content-Type: text/plain;\n";
+ $feed.= " charset=\"".$this->encoding."\"\n";
+ $feed.= "Content-Transfer-Encoding: quoted-printable\n";
+ $feed.= "Content-Type: text/plain\n";
+ $feed.= "From: \"".MBOXCreator::qp_enc($from)."\"\n";
+ $feed.= "Date: ".$itemDate->rfc822()."\n";
+ $feed.= "Subject: ".MBOXCreator::qp_enc(FeedCreator::iTrunc($this->items[$i]->title,100))."\n";
+ $feed.= "\n";
+ $body = chunk_split(MBOXCreator::qp_enc($this->items[$i]->description));
+ $feed.= preg_replace("~\nFrom ([^\n]*)(\n?)~","\n>From $1$2\n",$body);
+ $feed.= "\n";
+ $feed.= "\n";
+ }
+ return $feed;
+ }
+
+ /**
+ * Generate a filename for the feed cache file. Overridden from FeedCreator to prevent XML data types.
+ * @return string the feed cache filename
+ * @since 1.4
+ * @access private
+ */
+ function _generateFilename() {
+ $fileInfo = pathinfo($_SERVER["PHP_SELF"]);
+ return substr($fileInfo["basename"],0,-(strlen($fileInfo["extension"])+1)).".mbox";
+ }
+}
+
+
+/**
+ * OPMLCreator is a FeedCreator that implements OPML 1.0.
+ *
+ * @see http://opml.scripting.com/spec
+ * @author Dirk Clemens, Kai Blankenhorn
+ * @since 1.5
+ */
+class OPMLCreator extends FeedCreator {
+
+ function OPMLCreator() {
+ $this->encoding = "utf-8";
+ }
+
+ function createFeed() {
+ $feed = "encoding."\"?>\n";
+ $feed.= $this->_createGeneratorComment();
+ $feed.= $this->_createStylesheetReferences();
+ $feed.= "\n";
+ $feed.= " \n";
+ $feed.= " ".htmlspecialchars($this->title)."\n";
+ if ($this->pubDate!="") {
+ $date = new FeedDate($this->pubDate);
+ $feed.= " ".$date->rfc822()."\n";
+ }
+ if ($this->lastBuildDate!="") {
+ $date = new FeedDate($this->lastBuildDate);
+ $feed.= " ".$date->rfc822()."\n";
+ }
+ if ($this->editor!="") {
+ $feed.= " ".$this->editor."\n";
+ }
+ if ($this->editorEmail!="") {
+ $feed.= " ".$this->editorEmail."\n";
+ }
+ $feed.= " \n";
+ $feed.= " \n";
+ for ($i=0;$iitems);$i++) {
+ $feed.= " items[$i]->title,"\n\r"," ")));
+ $feed.= " title=\"".$title."\"";
+ $feed.= " text=\"".$title."\"";
+ //$feed.= " description=\"".htmlspecialchars($this->items[$i]->description)."\"";
+ $feed.= " url=\"".htmlspecialchars($this->items[$i]->link)."\"";
+ $feed.= "/>\n";
+ }
+ $feed.= " \n";
+ $feed.= "\n";
+ return $feed;
+ }
+}
+
+
+
+/**
+ * HTMLCreator is a FeedCreator that writes an HTML feed file to a specific
+ * location, overriding the createFeed method of the parent FeedCreator.
+ * The HTML produced can be included over http by scripting languages, or serve
+ * as the source for an IFrame.
+ * All output by this class is embedded in tags to enable formatting
+ * using CSS.
+ *
+ * @author Pascal Van Hecke
+ * @since 1.7
+ */
+class HTMLCreator extends FeedCreator {
+
+ var $contentType = "text/html";
+
+ /**
+ * Contains HTML to be output at the start of the feed's html representation.
+ */
+ var $header;
+
+ /**
+ * Contains HTML to be output at the end of the feed's html representation.
+ */
+ var $footer ;
+
+ /**
+ * Contains HTML to be output between entries. A separator is only used in
+ * case of multiple entries.
+ */
+ var $separator;
+
+ /**
+ * Used to prefix the stylenames to make sure they are unique
+ * and do not clash with stylenames on the users' page.
+ */
+ var $stylePrefix;
+
+ /**
+ * Determines whether the links open in a new window or not.
+ */
+ var $openInNewWindow = true;
+
+ var $imageAlign ="right";
+
+ /**
+ * In case of very simple output you may want to get rid of the style tags,
+ * hence this variable. There's no equivalent on item level, but of course you can
+ * add strings to it while iterating over the items ($this->stylelessOutput .= ...)
+ * and when it is non-empty, ONLY the styleless output is printed, the rest is ignored
+ * in the function createFeed().
+ */
+ var $stylelessOutput ="";
+
+ /**
+ * Writes the HTML.
+ * @return string the scripts's complete text
+ */
+ function createFeed() {
+ // if there is styleless output, use the content of this variable and ignore the rest
+ if ($this->stylelessOutput!="") {
+ return $this->stylelessOutput;
+ }
+
+ //if no stylePrefix is set, generate it yourself depending on the script name
+ if ($this->stylePrefix=="") {
+ $this->stylePrefix = str_replace(".", "_", $this->_generateFilename())."_";
+ }
+
+ //set an openInNewWindow_token_to be inserted or not
+ if ($this->openInNewWindow) {
+ $targetInsert = " target='_blank'";
+ }
+
+ // use this array to put the lines in and implode later with "document.write" javascript
+ $feedArray = array();
+ if ($this->image!=null) {
+ $imageStr = "".
+ "image->width) {
+ $imageStr .=" width='".$this->image->width. "' ";
+ }
+ if ($this->image->height) {
+ $imageStr .=" height='".$this->image->height."' ";
+ }
+ $imageStr .="/>";
+ $feedArray[] = $imageStr;
+ }
+
+ if ($this->title) {
+ $feedArray[] = "
';
+ return $ret;
+}
+
+
+function img_cache(&$csrc,&$src,&$w,&$h,$nocache){
+ global $conf;
+
+ //container for various paths
+ $f['full']['web'] = $src;
+ $f['resz']['web'] = $src;
+ $f['full']['fs'] = $src;
+ $f['resz']['fs'] = $src;
+
+ //generate cachename
+ $md5 = md5($src);
+
+ //check if it is an image
+ if(preg_match('#\.(jpe?g|gif|png)$#i',$src,$match)){
+ $ext = strtolower($match[1]);
+ $isimg = true;
+ }
+
+ //check if it is external or a local mediafile
+ if(preg_match('#^([a-z0-9]+?)://#i',$src)){
+ $isurl = true;
+ }else{
+ $src = str_replace(':','/',$src);
+ $f['full']['web'] = $conf['mediaweb'].'/'.$src;
+ $f['resz']['web'] = $conf['mediaweb'].'/'.$src;
+ $f['full']['fs'] = $conf['mediadir'].'/'.$src;
+ $f['resz']['fs'] = $conf['mediadir'].'/'.$src;
+ }
+
+ //download external images if allowed
+ if($isurl && $isimg && !$nocache){
+ $cache = $conf['mediadir']."/.cache/$md5.$ext";
+ if (@file_exists($cache) || download($src,$cache)){
+ $f['full']['web'] = $conf['mediaweb']."/.cache/$md5.$ext";
+ $f['resz']['web'] = $conf['mediaweb']."/.cache/$md5.$ext";
+ $f['full']['fs'] = $conf['mediadir']."/.cache/$md5.$ext";
+ $f['resz']['fs'] = $conf['mediadir']."/.cache/$md5.$ext";
+ $isurl = false;
+ }
+ }
+
+ //for local images (cached or media) do resizing
+ if($isimg && (!$isurl) && $w){
+ $info = getImageSize($f['full']['fs']);
+ //if $h not given calcualte it with correct aspect ratio
+ if(!$h){
+ $h = round(($w * $info[1]) / $info[0]);
+ }
+ $cache = $conf['mediadir'].'/.cache/'.$md5.'.'.$w.'x'.$h.'.'.$ext;
+ //delete outdated cachefile
+ if(@file_exists($cache) && (filemtime($cache)
diff --git a/inc/geshi.php b/inc/geshi.php
new file mode 100644
index 000000000..c1231fb59
--- /dev/null
+++ b/inc/geshi.php
@@ -0,0 +1,2413 @@
+ 'No source code inputted',
+ GESHI_ERROR_NO_SUCH_LANG => 'GeSHi could not find the language {LANGUAGE} (using path {PATH})'
+);
+
+// Line numbers - use with enable_line_numbers()
+define('GESHI_NO_LINE_NUMBERS', 0);
+define('GESHI_NORMAL_LINE_NUMBERS', 1);
+define('GESHI_FANCY_LINE_NUMBERS', 2);
+
+// Strict mode - shouldn't be used by your scripts
+define('GESHI_NEVER', 0);
+define('GESHI_MAYBE', 1);
+define('GESHI_ALWAYS', 2);
+
+// Container HTML type - use these (added in 1.0.1)
+define('GESHI_HEADER_DIV', 1);
+define('GESHI_HEADER_PRE', 2);
+
+// Capatalisation constants - use these (added in 1.0.1)
+define('GESHI_CAPS_NO_CHANGE', 0);
+define('GESHI_CAPS_UPPER', 1);
+define('GESHI_CAPS_LOWER', 2);
+
+// Link style constants - use these (added in 1.0.2)
+define('GESHI_LINK', 0);
+define('GESHI_HOVER', 1);
+define('GESHI_ACTIVE', 2);
+define('GESHI_VISITED', 3);
+
+// Important string starter/finisher - use these (added in 1.0.2).
+// Note that if you change these, they should be as-is: i.e., don't
+// write them as if they had been run through htmlentities()
+define('GESHI_START_IMPORTANT', '');
+define('GESHI_END_IMPORTANT', '');
+
+// Advanced regexp handling - don't use these (added in 1.0.2)
+define('GESHI_SEARCH', 0);
+define('GESHI_REPLACE', 1);
+define('GESHI_MODIFIERS', 2);
+define('GESHI_BEFORE', 3);
+define('GESHI_AFTER', 4);
+
+// Begin Class GeSHi
+class GeSHi
+{
+ //
+ // Data Fields
+ //
+
+ // Basic fields
+ var $source = ''; // The source code to highlight
+ var $language = ''; // The language to use when highlighting
+ var $language_data = array(); // The data for the language used
+ var $language_path = 'geshi/'; // The path to the language files
+ var $error = false; // The error message associated with an error
+ var $strict_mode = false; // Whether highlighting is strict or not
+ var $use_classes = false; // Whether to use classes
+ var $header_type = GESHI_HEADER_PRE; // The type of header to use
+ var $lexic_permissions = array(); // Array of permissions for which lexics should be highlighted
+ // Added in 1.0.2 basic fields
+ var $time = 0; // The time it took to parse the code
+ var $header_content = ''; // The content of the header block
+ var $footer_content = ''; // The content of the footer block
+ var $header_content_style = ''; // The style of the header block
+ var $footer_content_style = ''; // The style of the footer block
+ var $link_styles = array(); // The styles for hyperlinks in the code
+ var $enable_important_blocks = true; // Whether important blocks should be recognised or not
+ var $important_styles = 'font-weight: bold; color: red;'; // Styles for important parts of the code
+ var $add_ids = false; // Whether css IDs should be added to the code
+ var $highlight_extra_lines = array(); // Lines that should be highlighted extra
+ var $highlight_extra_lines_style = 'color: #cc0; background-color: #ffc;';// Styles of extra-highlighted lines
+ var $line_numbers_start = 1; // Number at which line numbers should start at
+
+ // Style fields
+ var $overall_style = ''; // The overall style for this code block
+ // The style for the actual code
+ var $code_style = 'font-family: \'Courier New\', Courier, monospace; font-weight: normal;';
+ var $overall_class = ''; // The overall class for this code block
+ var $overall_id = ''; // The overall id for this code block
+ // Line number styles
+ var $line_style1 = 'font-family: \'Courier New\', Courier, monospace; color: black; font-weight: normal; font-style: normal;';
+ var $line_style2 = 'font-weight: bold;';
+ var $line_numbers = GESHI_NO_LINE_NUMBERS; // Flag for how line numbers are displayed
+ var $line_nth_row = 0; // The "nth" value for fancy line highlighting
+
+ // Misc
+ var $tab_width = 8; // A value for the size of tab stops.
+ var $max_tabs = 20; // Maximum number of spaces per tab
+ var $min_tabs = 0; // Minimum " " " " "
+ var $link_target = ''; // default target for keyword links
+ var $encoding = ''; // The encoding to use for htmlentities() calls
+
+ // Deprecated/unused
+ var $output_format = GESHI_OUTPUT_HTML;
+
+
+ /**
+ * constructor: GeSHi
+ * ------------------
+ * Creates a new GeSHi object, with source and language
+ */
+ function GeSHi ($source, $language, $path = 'geshi/')
+ {
+ $this->source = $source;
+ // Security, just in case :)
+ $language = preg_replace('#[^a-zA-Z0-9\-\_]#', '', $language);
+ $this->language = strtolower($language);
+ $this->language_path = ( substr($path, strlen($path) - 1, 1) == '/' ) ? $path : $path . '/';
+ $this->load_language();
+ }
+
+
+ //
+ // Error methods
+ //
+
+ /**
+ * method: error
+ * -------------
+ * Returns an error message associated with the last GeSHi operation,
+ * or false if no error has occured
+ */
+ function error()
+ {
+ global $_GESHI_ERRORS;
+ if ( $this->error != 0 )
+ {
+ $msg = $_GESHI_ERRORS[$this->error];
+ $debug_tpl_vars = array(
+ '{LANGUAGE}' => $this->language,
+ '{PATH}' => $this->language_path
+ );
+ foreach ( $debug_tpl_vars as $tpl => $var )
+ {
+ $msg = str_replace($tpl, $var, $msg);
+ }
+ return " GeSHi Error: $msg (code $this->error) ";
+ }
+ return false;
+ }
+
+
+ //
+ // Getters
+ //
+
+ /**
+ * get_language_name()
+ * ---------------
+ * Gets a human-readable language name (thanks to Simon Patterson
+ * for the idea :))
+ */
+ function get_language_name()
+ {
+ if ( $this->error == GESHI_ERROR_NO_SUCH_LANG )
+ {
+ return $this->language_data['LANG_NAME'] . ' (Unknown Language)';
+ }
+ return $this->language_data['LANG_NAME'];
+ }
+
+
+ //
+ // Setters
+ //
+
+ /**
+ * method: set_source
+ * ------------------
+ * Sets the source code for this object
+ */
+ function set_source ( $source )
+ {
+ $this->source = $source;
+ }
+
+
+ /**
+ * method: set_language
+ * --------------------
+ * Sets the language for this object
+ */
+ function set_language ( $language )
+ {
+ $language = preg_replace('#[^a-zA-Z0-9\-_]#', '', $language);
+ $this->language = strtolower($language);
+ // Load the language for parsing
+ $this->load_language();
+ }
+
+
+ /**
+ * method: set_language_path
+ * -------------------------
+ * Sets the path to the directory containing the language files. NOTE
+ * that this path is relative to the directory of the script that included
+ * geshi.php, NOT geshi.php itself.
+ */
+ function set_language_path ( $path )
+ {
+ $this->language_path = ( substr($path, strlen($path) - 1, 1) == '/' ) ? $path : $path . '/';
+ }
+
+
+ /**
+ * method: set_header_type
+ * -----------------------
+ * Sets the type of header to be used. If GESHI_HEADER_DIV is used,
+ * the code is surrounded in a
. This means more source code but
+ * more control over tab width and line-wrapping. GESHI_HEADER_PRE
+ * means that a
is used - less source, but less control. Default
+ * is GESHI_HEADER_PRE
+ */
+ function set_header_type ( $type )
+ {
+ $this->header_type = $type;
+ }
+
+
+ /**
+ * method: set_overall_style
+ * -------------------------
+ * Sets the styles for the code that will be outputted
+ * when this object is parsed. The style should be a
+ * string of valid stylesheet declarations
+ */
+ function set_overall_style ( $style, $preserve_defaults = false )
+ {
+ if ( $preserve_defaults )
+ {
+ $this->overall_style .= $style;
+ }
+ else
+ {
+ $this->overall_style = $style;
+ }
+ }
+
+
+ /**
+ * method: set_overall_class
+ * -------------------------
+ * Sets the overall classname for this block of code. This
+ * class can then be used in a stylesheet to style this object's
+ * output
+ */
+ function set_overall_class ( $class )
+ {
+ $this->overall_class = $class;
+ }
+
+
+ /**
+ * method: set_overall_id
+ * ----------------------
+ * Sets the overall id for this block of code. This id can then
+ * be used in a stylesheet to style this object's output
+ */
+ function set_overall_id ( $id )
+ {
+ $this->overall_id = $id;
+ }
+
+
+ /**
+ * method: enable_classes
+ * ----------------------
+ * Sets whether CSS classes should be used to highlight the source. Default
+ * is off, calling this method with no arguments will turn it on
+ */
+ function enable_classes ( $flag = true )
+ {
+ $this->use_classes = ( $flag ) ? true : false;
+ }
+
+
+ /**
+ * method: set_code_style
+ * ----------------------
+ * Sets the style for the actual code. This should be a string
+ * containing valid stylesheet declarations. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ *
+ * NOTE: Use this method to override any style changes you made to
+ * the line numbers if you are using line numbers, else the line of
+ * code will have the same style as the line number! Consult the
+ * GeSHi documentation for more information about this.
+ */
+ function set_code_style ( $style, $preserve_defaults )
+ {
+ if ( $preserve_defaults )
+ {
+ $this->code_style .= $style;
+ }
+ else
+ {
+ $this->code_style = $style;
+ }
+ }
+
+
+ /**
+ * method: set_line_style
+ * ----------------------
+ * Sets the styles for the line numbers. This should be a string
+ * containing valid stylesheet declarations. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ */
+ function set_line_style ( $style1, $style2 = '', $preserve_defaults = false )
+ {
+ if ( is_bool($style2) )
+ {
+ $preserve_defaults = $style2;
+ $style2 = '';
+ }
+ if ( $preserve_defaults )
+ {
+ $this->line_style1 .= $style1;
+ $this->line_style2 .= $style2;
+ }
+ else
+ {
+ $this->line_style1 = $style1;
+ $this->line_style2 = $style2;
+ }
+ }
+
+
+ /**
+ * method: enable_line_numbers
+ * ---------------------------
+ * Sets whether line numbers should be displayed. GESHI_NO_LINE_NUMBERS = not displayed,
+ * GESHI_NORMAL_LINE_NUMBERS = displayed, GESHI_FANCY_LINE_NUMBERS = every nth line a
+ * different class. Default is for no line numbers to be used
+ */
+ function enable_line_numbers ( $flag, $nth_row = 5 )
+ {
+ $this->line_numbers = $flag;
+ $this->line_nth_row = $nth_row;
+ }
+
+
+ /**
+ * method: set_keyword_group_style
+ * -------------------------------
+ * Sets the style for a keyword group. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ */
+ function set_keyword_group_style ( $key, $style, $preserve_defaults = false )
+ {
+ if ( $preserve_defaults )
+ {
+ $this->language_data['STYLES']['KEYWORDS'][$key] .= $style;
+ }
+ else
+ {
+ $this->language_data['STYLES']['KEYWORDS'][$key] = $style;
+ }
+ }
+
+
+ /**
+ * method: set_keyword_group_highlighting
+ * --------------------------------------
+ * Turns highlighting on/off for a keyword group
+ */
+ function set_keyword_group_highlighting ( $key, $flag = true )
+ {
+ $this->lexic_permissions['KEYWORDS'][$key] = ( $flag ) ? true : false;
+ }
+
+
+ /**
+ * method: set_comments_style
+ * --------------------------
+ * Sets the styles for comment groups. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ */
+ function set_comments_style ( $key, $style, $preserve_defaults = false )
+ {
+ if ( $preserve_defaults )
+ {
+ $this->language_data['STYLES']['COMMENTS'][$key] .= $style;
+ }
+ else
+ {
+ $this->language_data['STYLES']['COMMENTS'][$key] = $style;
+ }
+ }
+
+
+ /**
+ * method: set_comments_highlighting
+ * ---------------------------------
+ * Turns highlighting on/off for comment groups
+ */
+ function set_comments_highlighting ( $key, $flag = true )
+ {
+ $this->lexic_permissions['COMMENTS'][$key] = ( $flag ) ? true : false;
+ }
+
+
+ /**
+ * method: set_escape_characters_style
+ * -----------------------------------
+ * Sets the styles for escaped characters. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ */
+ function set_escape_characters_style ( $style, $preserve_defaults = false )
+ {
+ if ( $preserve_defaults )
+ {
+ $this->language_data['STYLES']['ESCAPE_CHAR'][0] .= $style;
+ }
+ else
+ {
+ $this->language_data['STYLES']['ESCAPE_CHAR'][0] = $style;
+ }
+ }
+
+
+ /**
+ * method: set_escape_characters_highlighting
+ * ------------------------------------------
+ * Turns highlighting on/off for escaped characters
+ */
+ function set_escape_characters_highlighting ( $flag = true )
+ {
+ $this->lexic_permissions['ESCAPE_CHAR'] = ( $flag ) ? true : false;
+ }
+
+
+ /**
+ * method: set_brackets_style
+ * --------------------------
+ * Sets the styles for brackets. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ *
+ * This method is DEPRECATED: use set_symbols_style instead.
+ * This method will be removed in 1.2.X
+ */
+ function set_brackets_style ( $style, $preserve_defaults = false )
+ {
+ if ( $preserve_defaults )
+ {
+ $this->language_data['STYLES']['BRACKETS'][0] .= $style;
+ }
+ else
+ {
+ $this->language_data['STYLES']['BRACKETS'][0] = $style;
+ }
+ }
+
+
+ /**
+ * method: set_brackets_highlighting
+ * ---------------------------------
+ * Turns highlighting on/off for brackets
+ *
+ * This method is DEPRECATED: use set_symbols_highlighting instead.
+ * This method will be remove in 1.2.X
+ */
+ function set_brackets_highlighting ( $flag )
+ {
+ $this->lexic_permissions['BRACKETS'] = ( $flag ) ? true : false;
+ }
+
+
+ /**
+ * method: set_symbols_style
+ * --------------------------
+ * Sets the styles for symbols. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ */
+ function set_symbols_style ( $style, $preserve_defaults = false )
+ {
+ if ( $preserve_defaults )
+ {
+ $this->language_data['STYLES']['SYMBOLS'][0] .= $style;
+ }
+ else
+ {
+ $this->language_data['STYLES']['SYMBOLS'][0] = $style;
+ }
+ // For backward compatibility
+ $this->set_brackets_style ( $style, $preserve_defaults );
+ }
+
+
+ /**
+ * method: set_symbols_highlighting
+ * ---------------------------------
+ * Turns highlighting on/off for symbols
+ */
+ function set_symbols_highlighting ( $flag )
+ {
+ $this->lexic_permissions['SYMBOLS'] = ( $flag ) ? true : false;
+ // For backward compatibility
+ $this->set_brackets_highlighting ( $flag );
+ }
+
+
+ /**
+ * method: set_strings_style
+ * -------------------------
+ * Sets the styles for strings. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ */
+ function set_strings_style ( $style, $preserve_defaults = false )
+ {
+ if ( $preserve_defaults )
+ {
+ $this->language_data['STYLES']['STRINGS'][0] .= $style;
+ }
+ else
+ {
+ $this->language_data['STYLES']['STRINGS'][0] = $style;
+ }
+ }
+
+
+ /**
+ * method: set_strings_highlighting
+ * --------------------------------
+ * Turns highlighting on/off for strings
+ */
+ function set_strings_highlighting ( $flag )
+ {
+ $this->lexic_permissions['STRINGS'] = ( $flag ) ? true : false;
+ }
+
+
+ /**
+ * method: set_numbers_style
+ * -------------------------
+ * Sets the styles for numbers. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ */
+ function set_numbers_style ( $style, $preserve_defaults = false )
+ {
+ if ( $preserve_defaults )
+ {
+ $this->language_data['STYLES']['NUMBERS'][0] .= $style;
+ }
+ else
+ {
+ $this->language_data['STYLES']['NUMBERS'][0] = $style;
+ }
+ }
+
+
+ /**
+ * method: set_numbers_highlighting
+ * --------------------------------
+ * Turns highlighting on/off for numbers
+ */
+ function set_numbers_highlighting ( $flag )
+ {
+ $this->lexic_permissions['NUMBERS'] = ( $flag ) ? true : false;
+ }
+
+
+ /**
+ * method: set_methods_style
+ * -------------------------
+ * Sets the styles for methods. $key is a number that references the
+ * appropriate "object splitter" - see the language file for the language
+ * you are highlighting to get this number. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ */
+ function set_methods_style ( $key, $style, $preserve_defaults = false )
+ {
+ if ( $preserve_defaults )
+ {
+ $this->language_data['STYLES']['METHODS'][$key] .= $style;
+ }
+ else
+ {
+ $this->language_data['STYLES']['METHODS'][$key] = $style;
+ }
+ }
+
+
+ /**
+ * method: set_methods_highlighting
+ * --------------------------------
+ * Turns highlighting on/off for methods
+ */
+ function set_methods_highlighting ( $flag )
+ {
+ $this->lexic_permissions['METHODS'] = ( $flag ) ? true : false;
+ }
+
+
+ /**
+ * method: set_regexps_style
+ * -------------------------
+ * Sets the styles for regexps. If $preserve_defaults is
+ * true, then styles are merged with the default styles, with the
+ * user defined styles having priority
+ */
+ function set_regexps_style ( $key, $style, $preserve_defaults = false )
+ {
+ if ( $preserve_defaults )
+ {
+ $this->language_data['STYLES']['REGEXPS'][$key] .= $style;
+ }
+ else
+ {
+ $this->language_data['STYLES']['REGEXPS'][$key] = $style;
+ }
+ }
+
+
+ /**
+ * method: set_regexps_highlighting
+ * --------------------------------
+ * Turns highlighting on/off for regexps
+ */
+ function set_regexps_highlighting ( $key, $flag )
+ {
+ $this->lexic_permissions['REGEXPS'][$key] = ( $flag ) ? true : false;
+ }
+
+
+ /**
+ * method: set_case_sensitivity
+ * ----------------------------
+ * Sets whether a set of keywords are checked for in a case sensitive manner
+ */
+ function set_case_sensitivity ( $key, $case )
+ {
+ $this->language_data['CASE_SENSITIVE'][$key] = ( $case ) ? true : false;
+ }
+
+
+ /**
+ * method: set_case_keywords
+ * -------------------------
+ * Sets the case that keywords should use when found. Use the constants:
+ * GESHI_CAPS_NO_CHANGE: leave keywords as-is
+ * GESHI_CAPS_UPPER: convert all keywords to uppercase where found
+ * GESHI_CAPS_LOWER: convert all keywords to lowercase where found
+ * Method added in 1.0.1
+ */
+ function set_case_keywords ( $case )
+ {
+ $this->language_data['CASE_KEYWORDS'] = $case;
+ }
+
+
+ /**
+ * method: set_tab_width
+ * ---------------------
+ * Sets how many spaces a tab is substituted for
+ * This method will probably be re-engineered later to allow customisability
+ * in the maximum and minimum number of tabs without mutulating data fields.
+ */
+ function set_tab_width ( $width )
+ {
+ if ( $width > $this->max_tabs ) $width = $this->max_tabs;
+ if ( $width < $this->min_tabs ) $width = $this->min_tabs;
+ $this->tab_width = $width;
+ }
+
+
+ /**
+ * method: enable_strict_mode
+ * --------------------------
+ * Enables/disables strict highlighting. Default is off, calling this
+ * method without parameters will turn it on. See documentation
+ * for more details on strict mode and where to use it
+ */
+ function enable_strict_mode ( $mode = true )
+ {
+ $this->strict_mode = ( $mode ) ? true : false;
+ // Turn on strict mode no matter what if language should always
+ // be in strict mode
+ if ( $this->language_data['STRICT_MODE_APPLIES'] == GESHI_ALWAYS )
+ {
+ $this->strict_mode = true;
+ }
+ // Turn off strict mode no matter what if language should never
+ // be in strict mode
+ elseif ( $this->language_data['STRICT_MODE_APPLIES'] == GESHI_NEVER )
+ {
+ $this->strict_mode = false;
+ }
+ }
+
+
+ /**
+ * method: disable_highlighting
+ * ----------------------------
+ * Disables all highlighting
+ */
+ function disable_highlighting ()
+ {
+ foreach ( $this->language_data['KEYWORDS'] as $key => $words )
+ {
+ $this->lexic_permissions['KEYWORDS'][$key] = false;
+ }
+ foreach ( $this->language_data['COMMENT_SINGLE'] as $key => $comment )
+ {
+ $this->lexic_permissions['COMMENTS'][$key] = false;
+ }
+ // Multiline comments
+ $this->lexic_permissions['COMMENTS']['MULTI'] = false;
+ // Escape characters
+ $this->lexic_permissions['ESCAPE_CHAR'] = false;
+ // Brackets
+ $this->lexic_permissions['BRACKETS'] = false;
+ // Strings
+ $this->lexic_permissions['STRINGS'] = false;
+ // Numbers
+ $this->lexic_permissions['NUMBERS'] = false;
+ // Methods
+ $this->lexic_permissions['METHODS'] = false;
+ // Symbols
+ $this->lexic_permissions['SYMBOLS'] = false;
+ // Script
+ $this->lexic_permissions['SCRIPT'] = false;
+ // Regexps
+ foreach ( $this->language_data['REGEXPS'] as $key => $regexp )
+ {
+ $this->lexic_permissions['REGEXPS'][$key] = false;
+ }
+ // Context blocks
+ $this->enable_important_blocks = false;
+ }
+
+
+ /**
+ * method: enable_highlighting
+ * ---------------------------
+ * Enables all highlighting
+ */
+ function enable_highlighting ()
+ {
+ foreach ( $this->language_data['KEYWORDS'] as $key => $words )
+ {
+ $this->lexic_permissions['KEYWORDS'][$key] = true;
+ }
+ foreach ( $this->language_data['COMMENT_SINGLE'] as $key => $comment )
+ {
+ $this->lexic_permissions['COMMENTS'][$key] = true;
+ }
+ // Multiline comments
+ $this->lexic_permissions['COMMENTS']['MULTI'] = true;
+ // Escape characters
+ $this->lexic_permissions['ESCAPE_CHAR'] = true;
+ // Brackets
+ $this->lexic_permissions['BRACKETS'] = true;
+ // Strings
+ $this->lexic_permissions['STRINGS'] = true;
+ // Numbers
+ $this->lexic_permissions['NUMBERS'] = true;
+ // Methods
+ $this->lexic_permissions['METHODS'] = true;
+ // Symbols
+ $this->lexic_permissions['SYMBOLS'] = true;
+ // Script
+ $this->lexic_permissions['SCRIPT'] = true;
+ // Regexps
+ foreach ( $this->language_data['REGEXPS'] as $key => $regexp )
+ {
+ $this->lexic_permissions['REGEXPS'][$key] = true;
+ }
+ // Context blocks
+ $this->enable_important_blocks = true;
+ }
+
+
+ /**
+ * method: add_keyword
+ * -------------------
+ * Adds a keyword to a keyword group for highlighting
+ */
+ function add_keyword( $key, $word )
+ {
+ $this->language_data['KEYWORDS'][$key][] = $word;
+ }
+
+
+ /**
+ * method: remove_keyword
+ * ----------------------
+ * Removes a keyword from a keyword group
+ */
+ function remove_keyword ( $key, $word )
+ {
+ $this->language_data['KEYWORDS'][$key] = array_diff($this->language_data['KEYWORDS'][$key], array($word));
+ }
+
+
+ /**
+ * method: add_keyword_group
+ * -------------------------
+ * Creates a new keyword group
+ */
+ function add_keyword_group ( $key, $styles, $case_sensitive = true, $words = array() )
+ {
+ if ( !is_array($words) )
+ {
+ $words = array($words);
+ }
+ $this->language_data['KEYWORDS'][$key] = $words;
+ $this->lexic_permissions['KEYWORDS'][$key] = true;
+ $this->language_data['CASE_SENSITIVE'][$key] = $case_sensitive;
+ $this->language_data['STYLES']['KEYWORDS'][$key] = $styles;
+ }
+
+
+ /**
+ * method: remove_keyword_group
+ * ----------------------------
+ * Removes a keyword group
+ */
+ function remove_keyword_group ( $key )
+ {
+ unset($this->language_data['KEYWORDS'][$key]);
+ unset($this->lexic_permissions['KEYWORDS'][$key]);
+ unset($this->language_data['CASE_SENSITIVE'][$key]);
+ unset($this->language_data['STYLES']['KEYWORDS'][$key]);
+ }
+
+
+ /**
+ * method: set_header_content
+ * --------------------------
+ * Sets the content of the header block
+ */
+ function set_header_content ( $content )
+ {
+ $this->header_content = $content;
+ }
+
+
+ /**
+ * method: set_footer_content
+ * --------------------------
+ * Sets the content of the footer block
+ */
+ function set_footer_content ( $content )
+ {
+ $this->footer_content = $content;
+ }
+
+
+ /**
+ * method: set_header_content_style
+ * --------------------------------
+ * Sets the style for the header content
+ */
+ function set_header_content_style ( $style )
+ {
+ $this->header_content_style = $style;
+ }
+
+
+ /**
+ * method: set_footer_content_style
+ * --------------------------------
+ * Sets the style for the footer content
+ */
+ function set_footer_content_style ( $style )
+ {
+ $this->footer_content_style = $style;
+ }
+
+
+ /**
+ * method: set_url_for_keyword_group
+ * ---------------------------------
+ * Sets the base URL to be used for keywords
+ */
+ function set_url_for_keyword_group ( $group, $url )
+ {
+ $this->language_data['URLS'][$group] = $url;
+ }
+
+
+ /**
+ * method: set_link_styles
+ * -----------------------
+ * Sets styles for links in code
+ */
+ function set_link_styles ( $type, $styles )
+ {
+ $this->link_styles[$type] = $styles;
+ }
+
+
+ /**
+ * method: set_link_target
+ * -----------------------
+ * Sets the target for links in code
+ */
+ function set_link_target ( $target )
+ {
+ if ( empty( $target ) )
+ {
+ $this->link_target = '';
+ }
+ else
+ {
+ $this->link_target = ' target="' . $target . '" ';
+ }
+ }
+
+
+ /**
+ * method: set_important_styles
+ * ----------------------------
+ * Sets styles for important parts of the code
+ */
+ function set_important_styles ( $styles )
+ {
+ $this->important_styles = $styles;
+ }
+
+
+ /**
+ * method: enable_important_blocks
+ * -------------------------------
+ * Sets whether context-important blocks are highlighted
+ */
+ function enable_important_blocks ( $flag )
+ {
+ $this->enable_important_blocks = ( $flag ) ? true : false;
+ }
+
+
+ /**
+ * method: enable_ids
+ * ------------------
+ * Whether CSS IDs should be added to each line
+ */
+ function enable_ids ( $flag = true )
+ {
+ $this->add_ids = ( $flag ) ? true : false;
+ }
+
+
+ /**
+ * method: highlight_lines_extra
+ * -----------------------------
+ * Specifies which lines to highlight extra
+ */
+ function highlight_lines_extra ( $lines )
+ {
+ if ( is_array($lines) )
+ {
+ foreach ( $lines as $line )
+ {
+ $this->highlight_extra_lines[intval($line)] = intval($line);
+ }
+ }
+ else
+ {
+ $this->highlight_extra_lines[intval($lines)] = intval($lines);
+ }
+ }
+
+
+ /**
+ * method: set_highlight_lines_extra_style
+ * ---------------------------------------
+ * Sets the style for extra-highlighted lines
+ */
+ function set_highlight_lines_extra_style ( $styles )
+ {
+ $this->highlight_extra_lines_style = $styles;
+ }
+
+
+ /**
+ * method: start_line_numbers_at
+ * -----------------------------
+ * Sets what number line numbers should start at. Should
+ * be a positive integer, and will be converted to one.
+ */
+ function start_line_numbers_at ( $number )
+ {
+ $this->line_numbers_start = abs(intval($number));
+ }
+
+
+ /**
+ * method: set_encoding
+ * --------------------
+ * Sets the encoding used for htmlentities(), for international
+ * support.
+ */
+ function set_encoding ( $encoding )
+ {
+ $this->encoding = $encoding;
+ }
+
+
+ /**
+ * method: parse_code()
+ * --------------------
+ * Returns the code in $this->source, highlighted and surrounded by the
+ * nessecary HTML. This should only be called ONCE, cos it's SLOW!
+ * If you want to highlight the same source multiple times, you're better
+ * off doing a whole lot of str_replaces to replace the s
+ */
+ function parse_code()
+ {
+ // Start the timer
+ $start_time = microtime();
+
+ // Firstly, if there is an error, we won't highlight
+ // FUTURE: maybe an option to try to force highlighting anyway?
+ if ( $this->error )
+ {
+ $result = $this->header();
+ if ( $this->header_type != GESHI_HEADER_PRE )
+ {
+ $result .= $this->indent(htmlentities($this->source, ENT_COMPAT, $this->encoding));
+ }
+ else
+ {
+ $result .= htmlentities($this->source, ENT_COMPAT, $this->encoding);
+ }
+ // Stop Timing
+ $this->set_time($start_time, microtime());
+ return $result . $this->footer();
+ }
+
+ // Add spaces for regular expression matching and line numbers
+ $code = ' ' . $this->source . ' ';
+ // Replace all newlines to a common form.
+ $code = str_replace("\r\n", "\n", $code);
+ $code = str_replace("\r", "\n", $code);
+
+ // Initialise various stuff
+ $length = strlen($code);
+ $STRING_OPEN = '';
+ $CLOSE_STRING = false;
+ $ESCAPE_CHAR_OPEN = false;
+ $COMMENT_MATCHED = false;
+ // Turn highlighting on if strict mode doesn't apply to this language
+ $HIGHLIGHTING_ON = ( !$this->strict_mode ) ? true : '';
+ // Whether to highlight inside a block of code
+ $HIGHLIGHT_INSIDE_STRICT = false;
+ $stuff_to_parse = '';
+ $result = '';
+
+ // "Important" selections are handled like multiline comments
+ if ( $this->enable_important_blocks )
+ {
+ $this->language_data['COMMENT_MULTI'][GESHI_START_IMPORTANT] = GESHI_END_IMPORTANT;
+ }
+
+
+ if ( $this->strict_mode )
+ {
+ // Break the source into bits. Each bit will be a portion of the code
+ // within script delimiters - for example, HTML between < and >
+ $parts = array(0 => array(0 => ''));
+ $k = 0;
+ for ( $i = 0; $i < $length; $i++ )
+ {
+ $char = substr($code, $i, 1);
+ if ( !$HIGHLIGHTING_ON )
+ {
+ foreach ( $this->language_data['SCRIPT_DELIMITERS'] as $key => $delimiters )
+ {
+ foreach ( $delimiters as $open => $close )
+ {
+ // Get the next little bit for this opening string
+ $check = substr($code, $i, strlen($open));
+ // If it matches...
+ if ( $check == $open )
+ {
+ // We start a new block with the highlightable
+ // code in it
+ $HIGHLIGHTING_ON = $open;
+ $i += strlen($open) - 1;
+ ++$k;
+ $char = $open;
+ $parts[$k][0] = $char;
+
+ // No point going around again...
+ break(2);
+ }
+ }
+ }
+ }
+ else
+ {
+ foreach ( $this->language_data['SCRIPT_DELIMITERS'] as $key => $delimiters )
+ {
+ foreach ( $delimiters as $open => $close )
+ {
+ if ( $open == $HIGHLIGHTING_ON )
+ {
+ // Found the closing tag
+ break(2);
+ }
+ }
+ }
+ // We check code from our current position BACKWARDS. This is so
+ // the ending string for highlighting can be included in the block
+ $check = substr($code, $i - strlen($close) + 1, strlen($close));
+ if ( $check == $close )
+ {
+ $HIGHLIGHTING_ON = '';
+ // Add the string to the rest of the string for this part
+ $parts[$k][1] = ( isset($parts[$k][1]) ) ? $parts[$k][1] . $char : $char;
+ ++$k;
+ $parts[$k][0] = '';
+ $char = '';
+ }
+ }
+ $parts[$k][1] = ( isset($parts[$k][1]) ) ? $parts[$k][1] . $char : $char;
+ }
+ $HIGHLIGHTING_ON = '';
+ }
+ else
+ {
+ // Not strict mode - simply dump the source into
+ // the array at index 1 (the first highlightable block)
+ $parts = array(
+ 1 => array(
+ 0 => '',
+ 1 => $code
+ )
+ );
+ }
+
+ // Now we go through each part. We know that even-indexed parts are
+ // code that shouldn't be highlighted, and odd-indexed parts should
+ // be highlighted
+ foreach ( $parts as $key => $data )
+ {
+ $part = $data[1];
+ // If this block should be highlighted...
+ if ( $key % 2 )
+ {
+ if ( $this->strict_mode )
+ {
+ // Find the class key for this block of code
+ foreach ( $this->language_data['SCRIPT_DELIMITERS'] as $script_key => $script_data )
+ {
+ foreach ( $script_data as $open => $close )
+ {
+ if ( $data[0] == $open )
+ {
+ break(2);
+ }
+ }
+ }
+
+ if ( $this->language_data['STYLES']['SCRIPT'][$script_key] != '' && $this->lexic_permissions['SCRIPT'] )
+ {
+ // Add a span element around the source to
+ // highlight the overall source block
+ if ( !$this->use_classes && $this->language_data['STYLES']['SCRIPT'][$script_key] != '' )
+ {
+ $attributes = ' style="' . $this->language_data['STYLES']['SCRIPT'][$script_key] . '"';
+ }
+ else
+ {
+ $attributes = ' class="sc' . $script_key . '"';
+ }
+ $result .= "";
+ }
+ }
+
+ if ( !$this->strict_mode || $this->language_data['HIGHLIGHT_STRICT_BLOCK'][$script_key] )
+ {
+ // Now, highlight the code in this block. This code
+ // is really the engine of GeSHi (along with the method
+ // parse_non_string_part).
+ $length = strlen($part);
+ for ( $i = 0; $i < $length; $i++ )
+ {
+ // Get the next char
+ $char = substr($part, $i, 1);
+ // Is this char the newline and line numbers being used?
+ if ( ($this->line_numbers != GESHI_NO_LINE_NUMBERS || count($this->highlight_extra_lines) > 0) && $char == "\n" )
+ {
+ // If so, is there a string open? If there is, we should end it before
+ // the newline and begin it again (so when
s are put in the source
+ // remains XHTML compliant)
+ // NOTE TO SELF: This opens up possibility of config files specifying
+ // that languages can/cannot have multiline strings???
+ if ( $STRING_OPEN )
+ {
+ if ( !$this->use_classes )
+ {
+ $attributes = ' style="' . $this->language_data['STYLES']['STRINGS'][0] . '"';
+ }
+ else
+ {
+ $attributes = ' class="st0"';
+ }
+ $char = '
' . $char . "";
+ }
+ }
+ // Is this a match of a string delimiter?
+ elseif ( $char == $STRING_OPEN )
+ {
+ if ( ($this->lexic_permissions['ESCAPE_CHAR'] && $ESCAPE_CHAR_OPEN) || ($this->lexic_permissions['STRINGS'] && !$ESCAPE_CHAR_OPEN) )
+ {
+ $char .= '';
+ }
+ if ( !$ESCAPE_CHAR_OPEN )
+ {
+ $STRING_OPEN = '';
+ $CLOSE_STRING = true;
+ }
+ $ESCAPE_CHAR_OPEN = false;
+ }
+ // Is this the start of a new string?
+ elseif ( in_array( $char, $this->language_data['QUOTEMARKS'] ) && ($STRING_OPEN == '') && $this->lexic_permissions['STRINGS'] )
+ {
+ $STRING_OPEN = $char;
+ if ( !$this->use_classes )
+ {
+ $attributes = ' style="' . $this->language_data['STYLES']['STRINGS'][0] . '"';
+ }
+ else
+ {
+ $attributes = ' class="st0"';
+ }
+ $char = "" . $char;
+
+ $result .= $this->parse_non_string_part( $stuff_to_parse );
+ $stuff_to_parse = '';
+ }
+ // Is this an escape char?
+ elseif ( ($char == $this->language_data['ESCAPE_CHAR']) && ($STRING_OPEN != '') )
+ {
+ if ( !$ESCAPE_CHAR_OPEN )
+ {
+ $ESCAPE_CHAR_OPEN = true;
+ if ( $this->lexic_permissions['ESCAPE_CHAR'] )
+ {
+ if ( !$this->use_classes )
+ {
+ $attributes = ' style="' . $this->language_data['STYLES']['ESCAPE_CHAR'][0] . '"';
+ }
+ else
+ {
+ $attributes = ' class="es0"';
+ }
+ $char = "" . $char;
+ }
+ }
+ else
+ {
+ $ESCAPE_CHAR_OPEN = false;
+ if ( $this->lexic_permissions['ESCAPE_CHAR'] )
+ {
+ $char .= '';
+ }
+ }
+ }
+ elseif ( $ESCAPE_CHAR_OPEN )
+ {
+ if ( $this->lexic_permissions['ESCAPE_CHAR'] )
+ {
+ $char .= '';
+ }
+ $ESCAPE_CHAR_OPEN = false;
+ $test_str = $char;
+ }
+ elseif ( $STRING_OPEN == '' )
+ {
+ // Is this a multiline comment?
+ foreach ( $this->language_data['COMMENT_MULTI'] as $open => $close )
+ {
+ $com_len = strlen($open);
+ $test_str = substr( $part, $i, $com_len );
+ $test_str_match = $test_str;
+ if ( $open == $test_str )
+ {
+ $COMMENT_MATCHED = true;
+ if ( $this->lexic_permissions['COMMENTS']['MULTI'] || $test_str == GESHI_START_IMPORTANT )
+ {
+ if ( $test_str != GESHI_START_IMPORTANT )
+ {
+ if ( !$this->use_classes )
+ {
+ $attributes = ' style="' . $this->language_data['STYLES']['COMMENTS']['MULTI'] . '"';
+ }
+ else
+ {
+ $attributes = ' class="coMULTI"';
+ }
+ $test_str = "" . htmlentities($test_str, ENT_COMPAT, $this->encoding);
+ }
+ else
+ {
+ if ( !$this->use_classes )
+ {
+ $attributes = ' style="' . $this->important_styles . '"';
+ }
+ else
+ {
+ $attributes = ' class="imp"';
+ }
+ // We don't include the start of the comment if it's an
+ // "important" part
+ $test_str = "";
+ }
+ }
+ else
+ {
+ $test_str = htmlentities($test_str, ENT_COMPAT, $this->encoding);
+ }
+
+ $close_pos = strpos( $part, $close, $i + strlen($close) );
+
+ if ( $close_pos === false )
+ {
+ $close_pos = strlen($part);
+ }
+
+ // Short-cut through all the multiline code
+ $rest_of_comment = htmlentities(substr($part, $i + $com_len, $close_pos - $i), ENT_COMPAT, $this->encoding);
+ if ( ($this->lexic_permissions['COMMENTS']['MULTI'] || $test_str_match == GESHI_START_IMPORTANT) && ($this->line_numbers != GESHI_NO_LINE_NUMBERS || count($this->highlight_extra_lines) > 0) )
+ {
+ // strreplace to put close span and open span around multiline newlines
+ $test_str .= str_replace("\n", "\n", $rest_of_comment);
+ }
+ else
+ {
+ $test_str .= $rest_of_comment;
+ }
+
+ if ( $this->lexic_permissions['COMMENTS']['MULTI'] || $test_str_match == GESHI_START_IMPORTANT )
+ {
+ $test_str .= '';
+ }
+ $i = $close_pos + $com_len - 1;
+ // parse the rest
+ $result .= $this->parse_non_string_part( $stuff_to_parse );
+ $stuff_to_parse = '';
+ break;
+ }
+ }
+ // If we haven't matched a multiline comment, try single-line comments
+ if ( !$COMMENT_MATCHED )
+ {
+ foreach ( $this->language_data['COMMENT_SINGLE'] as $comment_key => $comment_mark )
+ {
+ $com_len = strlen($comment_mark);
+ $test_str = substr( $part, $i, $com_len );
+ if ( $this->language_data['CASE_SENSITIVE'][GESHI_COMMENTS] )
+ {
+ $match = ( $comment_mark == $test_str );
+ }
+ else
+ {
+ $match = ( strtolower($comment_mark) == strtolower($test_str) );
+ }
+ if ( $match )
+ {
+ $COMMENT_MATCHED = true;
+ if ( $this->lexic_permissions['COMMENTS'][$comment_key] )
+ {
+ if ( !$this->use_classes )
+ {
+ $attributes = ' style="' . $this->language_data['STYLES']['COMMENTS'][$comment_key] . '"';
+ }
+ else
+ {
+ $attributes = ' class="co' . $comment_key . '"';
+ }
+ $test_str = "" . htmlentities($this->change_case($test_str), ENT_COMPAT, $this->encoding);
+ }
+ else
+ {
+ $test_str = htmlentities($test_str, ENT_COMPAT, $this->encoding);
+ }
+ $close_pos = strpos( $part, "\n", $i );
+ if ( $close_pos === false )
+ {
+ $close_pos = strlen($part);
+ }
+ $test_str .= htmlentities(substr($part, $i + $com_len, $close_pos - $i - $com_len), ENT_COMPAT, $this->encoding);
+ if ( $this->lexic_permissions['COMMENTS'][$comment_key] )
+ {
+ $test_str .= "";
+ }
+ $test_str .= "\n";
+ $i = $close_pos;
+ // parse the rest
+ $result .= $this->parse_non_string_part( $stuff_to_parse );
+ $stuff_to_parse = '';
+ break;
+ }
+ }
+ }
+ }
+ // Otherwise, convert it to HTML form
+ elseif ( $STRING_OPEN != '' )
+ {
+ $char = htmlentities($char, ENT_COMPAT, $this->encoding);
+ }
+ // Where are we adding this char?
+ if ( !$COMMENT_MATCHED )
+ {
+ if ( ($STRING_OPEN == '') && !$CLOSE_STRING )
+ {
+ $stuff_to_parse .= $char;
+ }
+ else
+ {
+ $result .= $char;
+ $CLOSE_STRING = false;
+ }
+ }
+ else
+ {
+ $result .= $test_str;
+ $COMMENT_MATCHED = false;
+ }
+ }
+ // Parse the last bit
+ $result .= $this->parse_non_string_part( $stuff_to_parse );
+ $stuff_to_parse = '';
+ }
+ else
+ {
+ $result .= htmlentities($part, ENT_COMPAT, $this->encoding);
+ }
+ // Close the that surrounds the block
+ if ( $this->strict_mode && $this->lexic_permissions['SCRIPT'] )
+ {
+ $result .= '';
+ }
+ }
+ // Else not a block to highlight
+ else
+ {
+ $result .= htmlentities($part, ENT_COMPAT, $this->encoding);
+ }
+ }
+
+ // Parse the last stuff (redundant?)
+ $result .= $this->parse_non_string_part( $stuff_to_parse );
+
+ // Lop off the very first and last spaces
+ $result = substr($result, 1, strlen($result) - 1);
+
+ // Are we still in a string?
+ if ( $STRING_OPEN )
+ {
+ $result .= '';
+ }
+
+ // We're finished: stop timing
+ $this->set_time($start_time, microtime());
+
+ return $this->finalise($result);
+ }
+
+ /**
+ * method: indent
+ * --------------
+ * Swaps out spaces and tabs for HTML indentation. Not needed if
+ * the code is in a pre block...
+ */
+ function indent ( $result )
+ {
+ $result = str_replace(' ', ' ', $result);
+ $result = str_replace(' ', ' ', $result);
+ $result = str_replace("\n ", "\n ", $result);
+ $result = str_replace("\t", $this->get_tab_replacement(), $result);
+ if ( $this->line_numbers == GESHI_NO_LINE_NUMBERS )
+ {
+ $result = nl2br($result);
+ }
+ return $result;
+ }
+
+ /**
+ * method: change_case
+ * -------------------
+ * Changes the case of a keyword for those languages where a change is asked for
+ */
+ function change_case ( $instr )
+ {
+ if ( $this->language_data['CASE_KEYWORDS'] == GESHI_CAPS_UPPER )
+ {
+ return strtoupper($instr);
+ }
+ elseif ( $this->language_data['CASE_KEYWORDS'] == GESHI_CAPS_LOWER )
+ {
+ return strtolower($instr);
+ }
+ return $instr;
+ }
+
+
+ /**
+ * method: add_url_to_keyword
+ * --------------------------
+ * Adds a url to a keyword where needed.
+ * Added in 1.0.2
+ */
+ function add_url_to_keyword ( $keyword, $group, $start_or_end )
+ {
+ if ( isset($this->language_data['URLS'][$group]) && $this->language_data['URLS'][$group] != '' && substr($keyword, 0, 5) != '</' )
+ {
+ // There is a base group for this keyword
+
+ if ( $start_or_end == 'BEGIN' )
+ {
+ // HTML workaround... not good form (tm) but should work for 1.0.X
+ $keyword = ( substr($keyword, 0, 4) == '<' ) ? substr($keyword, 4) : $keyword;
+ $keyword = ( substr($keyword, -4) == '>' ) ? substr($keyword, 0, strlen($keyword) - 4) : $keyword;
+ if ( $keyword != '' )
+ {
+ $keyword = ( $this->language_data['CASE_SENSITIVE'][$group] ) ? $keyword : strtolower($keyword);
+ return '<|UR1|"' . str_replace(array('{FNAME}', '.'), array(htmlentities($keyword, ENT_COMPAT, $this->encoding), ''), $this->language_data['URLS'][$group]) . '">';
+ }
+ return '';
+ }
+ else
+ {
+ return '';
+ }
+ }
+ }
+
+
+ /**
+ * method: parse_non_string_part
+ * -----------------------------
+ * Takes a string that has no strings or comments in it, and highlights
+ * stuff like keywords, numbers and methods.
+ */
+ function parse_non_string_part ( &$stuff_to_parse )
+ {
+ $stuff_to_parse = ' ' . quotemeta(htmlentities($stuff_to_parse, ENT_COMPAT, $this->encoding));
+ // These vars will disappear in the future
+ $func = '$this->change_case';
+ $func2 = '$this->add_url_to_keyword';
+
+
+ //
+ // Regular expressions
+ //
+ foreach ( $this->language_data['REGEXPS'] as $key => $regexp )
+ {
+ if ( $this->lexic_permissions['REGEXPS'][$key] )
+ {
+ if ( is_array($regexp) )
+ {
+ $stuff_to_parse = preg_replace( "#" . $regexp[GESHI_SEARCH] . "#{$regexp[GESHI_MODIFIERS]}", "{$regexp[GESHI_BEFORE]}<|!REG3XP$key!>{$regexp[GESHI_REPLACE]}|>{$regexp[GESHI_AFTER]}", $stuff_to_parse);
+ }
+ else
+ {
+ $stuff_to_parse = preg_replace( "#(" . $regexp . ")#", "<|!REG3XP$key!>\\1|>", $stuff_to_parse);
+ }
+ }
+ }
+
+ //
+ // Highlight numbers. This regexp sucks... anyone with a regexp that WORKS
+ // here wins a cookie if they send it to me. At the moment there's two doing
+ // almost exactly the same thing, except the second one prevents a number
+ // being highlighted twice (eg 5)
+ // Put /NUM!/ in for the styles, which gets replaced at the end.
+ //
+ if ( $this->lexic_permissions['NUMBERS'] && preg_match('#[0-9]#', $stuff_to_parse ) )
+ {
+ $stuff_to_parse = preg_replace('#([^a-zA-Z0-9\#])([0-9]+)([^a-zA-Z0-9])#', "\\1<|/NUM!/>\\2|>\\3", $stuff_to_parse);
+ $stuff_to_parse = preg_replace('#([^a-zA-Z0-9\#>])([0-9]+)([^a-zA-Z0-9])#', "\\1<|/NUM!/>\\2|>\\3", $stuff_to_parse);
+ }
+
+ // Highlight keywords
+ // if there is a couple of alpha symbols there *might* be a keyword
+ if ( preg_match('#[a-zA-Z]{2,}#', $stuff_to_parse) )
+ {
+ foreach ( $this->language_data['KEYWORDS'] as $k => $keywordset )
+ {
+ if ( $this->lexic_permissions['KEYWORDS'][$k] )
+ {
+ foreach ( $keywordset as $keyword )
+ {
+ $keyword = quotemeta($keyword);
+ //
+ // This replacement checks the word is on it's own (except if brackets etc
+ // are next to it), then highlights it. We don't put the color=" for the span
+ // in just yet - otherwise languages with the keywords "color" or "or" have
+ // a fit.
+ //
+ if ( false !== stristr($stuff_to_parse, $keyword ) )
+ {
+ $stuff_to_parse .= ' ';
+ // Might make a more unique string for putting the number in soon
+ // Basically, we don't put the styles in yet because then the styles themselves will
+ // get highlighted if the language has a CSS keyword in it (like CSS, for example ;))
+ $styles = "/$k/";
+ $keyword = quotemeta($keyword);
+ if ( $this->language_data['CASE_SENSITIVE'][$k] )
+ {
+ $stuff_to_parse = preg_replace("#([^a-zA-Z0-9\$_\|\.\#;>])($keyword)([^a-zA-Z0-9_<\|%\-&])#e", "'\\1' . $func2('\\2', '$k', 'BEGIN') . '<|$styles>' . $func('\\2') . '|>' . $func2('\\2', '$k', 'END') . '\\3'", $stuff_to_parse);
+ }
+ else
+ {
+ // Change the case of the word.
+ $stuff_to_parse = preg_replace("#([^a-zA-Z0-9\$_\|\.\#;>])($keyword)([^a-zA-Z0-9_<\|%\-&])#ie", "'\\1' . $func2('\\2', '$k', 'BEGIN') . '<|$styles>' . $func('\\2') . '|>' . $func2('\\2', '$k', 'END') . '\\3'", $stuff_to_parse);
+ }
+ $stuff_to_parse = substr($stuff_to_parse, 0, strlen($stuff_to_parse) - 1);
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // Now that's all done, replace /[number]/ with the correct styles
+ //
+ foreach ( $this->language_data['KEYWORDS'] as $k => $kws )
+ {
+ if ( !$this->use_classes )
+ {
+ $attributes = ' style="' . $this->language_data['STYLES']['KEYWORDS'][$k] . '"';
+ }
+ else
+ {
+ $attributes = ' class="kw' . $k . '"';
+ }
+ $stuff_to_parse = str_replace("/$k/", $attributes, $stuff_to_parse);
+ }
+
+ // Put number styles in
+ if ( !$this->use_classes && $this->lexic_permissions['NUMBERS'] )
+ {
+ $attributes = ' style="' . $this->language_data['STYLES']['NUMBERS'][0] . '"';
+ }
+ else
+ {
+ $attributes = ' class="nu0"';
+ }
+ $stuff_to_parse = str_replace('/NUM!/', $attributes, $stuff_to_parse);
+
+ //
+ // Highlight methods and fields in objects
+ //
+ if ( $this->lexic_permissions['METHODS'] && $this->language_data['OOLANG'] )
+ {
+ foreach ( $this->language_data['OBJECT_SPLITTERS'] as $key => $splitter )
+ {
+ if ( false !== stristr($stuff_to_parse, $this->language_data['OBJECT_SPLITTERS'][$key]) )
+ {
+ if ( !$this->use_classes )
+ {
+ $attributes = ' style="' . $this->language_data['STYLES']['METHODS'][$key] . '"';
+ }
+ else
+ {
+ $attributes = ' class="me' . $key . '"';
+ }
+ $stuff_to_parse = preg_replace("#(" . quotemeta($this->language_data['OBJECT_SPLITTERS'][$key]) . "[\s]*)([a-zA-Z\*\(][a-zA-Z0-9_\*]*)#", "\\1<|$attributes>\\2|>", $stuff_to_parse);
+ }
+ }
+ }
+
+ //
+ // Highlight brackets. Yes, I've tried adding a semi-colon to this list.
+ // You try it, and see what happens ;)
+ // TODO: Fix lexic permissions not converting entities if shouldn't
+ // be highlighting regardless
+ //
+ if ( $this->lexic_permissions['BRACKETS'] )
+ {
+ $code_entities_match = array('[', ']', '(', ')', '{', '}');
+ if ( !$this->use_classes )
+ {
+ $code_entities_replace = array(
+ '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">[|>',
+ '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">]|>',
+ '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">(|>',
+ '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">)|>',
+ '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">{|>',
+ '<| style="' . $this->language_data['STYLES']['BRACKETS'][0] . '">}|>',
+ );
+ }
+ else
+ {
+ $code_entities_replace = array(
+ '<| class="br0">[|>',
+ '<| class="br0">]|>',
+ '<| class="br0">(|>',
+ '<| class="br0">)|>',
+ '<| class="br0">{|>',
+ '<| class="br0">}|>',
+ );
+ }
+ $stuff_to_parse = str_replace( $code_entities_match, $code_entities_replace, $stuff_to_parse );
+ }
+
+ //
+ // Add class/style for regexps
+ //
+ foreach ( $this->language_data['REGEXPS'] as $key => $regexp )
+ {
+ if ( $this->lexic_permissions['REGEXPS'][$key] )
+ {
+ if ( !$this->use_classes )
+ {
+ $attributes = ' style="' . $this->language_data['STYLES']['REGEXPS'][$key] . '"';
+ }
+ else
+ {
+ $attributes = ' class="re' . $key . '"';
+ }
+ $stuff_to_parse = str_replace("!REG3XP$key!", "$attributes", $stuff_to_parse);
+ }
+ }
+
+ // Replace with . for urls
+ $stuff_to_parse = str_replace('', '.', $stuff_to_parse);
+ // Replace <|UR1| with link_styles[GESHI_LINK]) )
+ {
+ if ( $this->use_classes )
+ {
+ $stuff_to_parse = str_replace('<|UR1|', 'link_target . ' href=', $stuff_to_parse);
+ }
+ else
+ {
+ $stuff_to_parse = str_replace('<|UR1|', 'link_target . ' style="' . $this->link_styles[GESHI_LINK] . '" href=', $stuff_to_parse);
+ }
+ }
+ else
+ {
+ $stuff_to_parse = str_replace('<|UR1|', 'link_target . ' href=', $stuff_to_parse);
+ }
+
+ //
+ // NOW we add the span thingy ;)
+ //
+
+ $stuff_to_parse = str_replace('<|', '', '', $stuff_to_parse );
+
+ return substr(stripslashes($stuff_to_parse), 1);
+ }
+
+ /**
+ * method: set_time
+ * ----------------
+ * Sets the time taken to parse the code
+ */
+ function set_time ( $start_time, $end_time )
+ {
+ $start = explode(' ', $start_time);
+ $end = explode(' ', $end_time);
+ $this->time = $end[0] + $end[1] - $start[0] - $start[1];
+ }
+
+ /**
+ * method: get_time
+ * ----------------
+ * Gets the time taken to parse the code
+ */
+ function get_time ()
+ {
+ return $this->time;
+ }
+
+ /**
+ * method: load_language
+ * ---------------------
+ * Gets language information and stores it for later use
+ */
+ function load_language ()
+ {
+ $file_name = $this->language_path . $this->language . '.php';
+ if ( !is_readable($file_name))
+ {
+ $this->error = GESHI_ERROR_NO_SUCH_LANG;
+ return;
+ }
+ require($file_name);
+ // Perhaps some checking might be added here later to check that
+ // $language data is a valid thing but maybe not
+ $this->language_data = $language_data;
+ // Set strict mode if should be set
+ if ( $this->language_data['STRICT_MODE_APPLIES'] == GESHI_ALWAYS )
+ {
+ $this->strict_mode = true;
+ }
+ // Set permissions for all lexics to true
+ // so they'll be highlighted by default
+ $this->enable_highlighting();
+ // Set default class for CSS
+ $this->overall_class = $this->language;
+ }
+
+ /**
+ * method: get_tab_replacement
+ * ---------------------------
+ * Gets the replacement string for tabs in the source code. Useful for
+ * HTML highlighting, where tabs don't mean anything to a browser.
+ */
+ function get_tab_replacement ()
+ {
+ $i = 0;
+ $result = '';
+ while ( $i < $this->tab_width )
+ {
+ $i++;
+ if ( $i % 2 == 0 )
+ {
+ $result .= ' ';
+ }
+ else
+ {
+ $result .= ' ';
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * method: finalise
+ * ----------------
+ * Takes the parsed code and various options, and creates the HTML
+ * surrounding it to make it look nice.
+ */
+ function finalise ( $parsed_code )
+ {
+ // Remove end parts of important declarations
+ // This is BUGGY!! My fault for bad code: fix coming in 1.2
+ if ( $this->enable_important_blocks && (strstr($parsed_code, htmlentities(GESHI_START_IMPORTANT, ENT_COMPAT, $this->encoding)) === false) )
+ {
+ $parsed_code = str_replace(htmlentities(GESHI_END_IMPORTANT, ENT_COMPAT, $this->encoding), '', $parsed_code);
+ }
+
+ // Add HTML whitespace stuff if we're using the