*
* A PageStore alternative which doesn't mangle page contents when viewed outside PmWiki
*
* Developed and tested using PmWiki 2.2.0
*
* To install, add the following to your configuration file:
include_once("$FarmD/cookbook/pagetopstore.php");
$WikiDir = new PageTopStore( 'wiki.d/{$FullName}', 'wikitop.d/{$FullName}' );
* For more information, please see the online documentation at
* http://www.pmwiki.org/wiki/Cookbook/PageTopStore
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License,
* Version 2, as published by the Free Software Foundation.
* http://www.gnu.org/copyleft/gpl.html
*/
$RecipeInfo['PageTopStore']['Version'] = '2009-11-27';
SDV( $HandleActions['filltop'], 'HandleFillTop' );
function HandleFillTop( $pagename, $auth = 'attr' ) {
global $ScriptUrl;
if (empty( $_GET['ps'] )) Abort( "No PageStore given!
usage: $ScriptUrl?action=filltop&ps=YourPageStoreVariableNameHere
use ps=WikiDir for default" );
$psn = $_GET['ps'];
if (empty( $GLOBALS[$psn]->topfmt )) Abort( "$psn at {$GLOBALS[$psn]->dirfmt} isn't a valid PageTopStore!" );
$page = RetrieveAuthPage( $pagename, $auth, true, READPAGE_CURRENT );
$GLOBALS[$psn]->fill();
}
class PageTopStore extends PageStore {
var $dirfmt;
var $topfmt;
var $iswrite;
var $attr;
var $author;
var $r0;
var $r1;
function PageTopStore( $d='$WorkDir/$FullName', $t='wikitop.d/{$FullName}', $w=0, $a=NULL, $u='[PageTopStore]', $r=array( '<'=>'<', '<'=>'<' ) ) {
$this->dirfmt = $d;
$this->topfmt = $t;
$this->iswrite = $w;
$this->attr = (array)$a;
$this->author = $u;
$this->r0 = array_keys((array)$r); ## '<' replacement prevents scripting attacks
$this->r1 = array_values((array)$r);
$GLOBALS['PageExistsCache'] = array();
}
## inherited: function pagefile( $pagename )
function pagetopfile( $pagename ) {
global $FarmD;
$tfmt = $this->topfmt;
if ($pagename > '') {
$pagename = str_replace('/', '.', $pagename);
## optimizations for standard locations
if ( $tfmt == 'wikitop.d/{$FullName}' ) return "wikitop.d/$pagename";
if ( $tfmt == 'wikitop.d/{$Group}/{$FullName}' ) return preg_replace( '/([^.]+).*/', 'wikitop.d/$1/$0', $pagename );
}
return FmtPageName( $tfmt, $pagename );
}
function update( $pagename, &$page, &$new ) {
global $Now, $EnablePost, $IsPagePosted, $Author;
Lock(2);
$prev_Now = $Now;
$prev_EnablePost = $EnablePost;
$prev_IsPagePosted = $IsPagePosted;
$prev_SERVER_REMOTE_ADDR = $_SERVER['REMOTE_ADDR'];
$prev_SERVER_HTTP_USER_AGENT = @$_SERVER['HTTP_USER_AGENT'];
$prev_Author = $Author;
$Now = min( $Now, max( $page['time'], $new['time'] ) );
$EnablePost = 1;
$_SERVER['REMOTE_ADDR'] = 'local';
$_SERVER['HTTP_USER_AGENT'] = 'PageTopStore';
$Author = $this->author;
UpdatePage( $pagename, $page, $new );
$Now = $prev_Now;
$EnablePost = $prev_EnablePost;
$IsPagePosted = $prev_IsPagePosted;
$_SERVER['REMOTE_ADDR'] = $prev_SERVER_REMOTE_ADDR;
$_SERVER['HTTP_USER_AGENT'] = $prev_SERVER_HTTP_USER_AGENT;
$Author = $prev_Author;
Lock(0);
}
function fixtop( $pagename ) {
global $Now;
$pagefile = $this->pagefile($pagename); if (empty($pagefile)) return;
$topfile = $this->pagetopfile($pagename); if (empty($topfile)) return;
if ( !file_exists($pagefile) ) {
if ( file_exists($topfile) ) {
$new = $this->read( $pagename, READPAGE_CURRENT );
$t = max( $new['time'], filemtime($topfile) );
$page = array( 'ctime' => $t, 'time' => $t );
$this->update( $pagename, $page, $new );
}
return;
}
if ( !file_exists($topfile) ) {
$page = parent::read( $pagename, READPAGE_CURRENT );
$this->writetop( $pagename, $page );
return;
}
if ( filemtime($topfile) > filemtime($pagefile) ) {
$page = parent::read( $pagename, 0 );
$new = array_merge( $page, $this->read( $pagename, READPAGE_CURRENT ) );
$new['version'] = $page['version'];
if ( $new != $page ) {
$new['time'] = max( $new['time'], filemtime($topfile) );
$this->update( $pagename, $page, $new );
}
}
}
function writetop( $pagename, &$page ) {
global $Now, $Version;
$topfile = $this->pagetopfile($pagename);
$dir = dirname($topfile);
mkdirp($dir);
if ( !file_exists("$dir/.htaccess") && ( $fp = @fopen( "$dir/.htaccess", 'w' ) ) ) {
fwrite( $fp, "Order Deny,Allow\nDeny from all\n" );
fclose($fp);
}
$st = FALSE;
if ( $topfile && ( $fp = fopen( "$topfile,new", 'w' ) ) ) {
$r0 = array( '%', "\n", '<' );
$r1 = array( '%25', '%0a', '%3c' );
$x = "version=$Version fmt=pagetop\n";
$st = true && fputs( $fp, $x );
$tz = strlen($x);
//if (empty($page)) $page = array( 'ctime' => $Now, 'time' => $Now );
uksort( $page, 'CmpPageAttr' );
foreach( $page as $k => $v ) if (
( $k > '' ) && ( $k[0] != '=' ) &&
( $k != 'version' ) && ( $k != 'text' ) && ( $k != 'newline' )
) {
if (strpos( $k, ':' )) break;
$x = str_replace( $r0, $r1, "$k=$v" ) . "\n";
$st = $st && fputs( $fp, $x );
$tz += strlen($x);
}
$text = str_replace( $this->r0, $this->r1, $page['text'] );
$st = $st && fputs( $fp, "\n$text\n" );
$tz += 2 + strlen($text);
$st = fclose($fp) && $st;
$st = $st && ( filesize("$topfile,new") > $tz * 0.95 );
if (file_exists( $topfile )) $st = $st && unlink($topfile);
$st = $st && rename( "$topfile,new", $topfile );
}
if ($st) {
fixperms($topfile);
touch( $topfile, $page['time'] );
} else Abort("Cannot write page $pagename top to ($topfile)...");
}
function fill() {
global $ScriptUrl;
print( "
\nFilling PageTopStore at {$this->topfmt} from PageStore at {$this->dirfmt}...\n" ); foreach( @parent::ls() as $pagename ) { print(" $pagename\n"); flush(); $page = parent::read( $pagename, READPAGE_CURRENT ); $this->writetop( $pagename, $page ); } print("\nall done.\n\n\n"); } function read( $pagename, $since=0 ) { global $EnablePageTopStoreAutofill; if ( $since != READPAGE_CURRENT ) { if (IsEnabled( $EnablePageTopStoreAutofill, TRUE )) $this->fixtop( $pagename ); return parent::read( $pagename, $since ); } $urlencoded = FALSE; $topfile = $this->pagetopfile($pagename); if ( $topfile && ( $ft = @fopen($topfile,'r') ) ) { $page = $this->attr; while ( !feof($ft) ) { ## headers $line = fgets( $ft, 4096 ); while ( ( substr( $line, -1, 1 ) != "\n" ) && !feof($ft) ) $line .= fgets( $ft, 4096 ); $line = rtrim($line); if (!$line) break; ## empty line indicates end of headers if ($urlencoded) $line = urldecode(str_replace( '+', '%2b', $line )); @list($k,$v) = explode( '=', $line, 2 ); if (!$k) continue; if ( $k == 'version' ) $urlencoded = ( strpos( $v, 'urlencoded=1' ) !== FALSE ); $page[$k] = $v; } //$page['text'] = stream_get_contents( $ft ); $page['text'] = ''; while (!feof( $ft )) $page['text'] .= fgets( $ft, 4096 ); $page['text'] = str_replace( $this->r1, $this->r0, $page['text'] ); if ( substr( $page['text'], -1 ) == "\n" ) $page['text'] = substr( $page['text'], 0, -1 ); fclose($ft); return $page; } $page = parent::read( $pagename, READPAGE_CURRENT ); if ( IsEnabled( $EnablePageTopStoreAutofill, TRUE ) && !empty($page) && $topfile && !file_exists($topfile) ) $this->writetop( $pagename, $page ); return $page; } function write( $pagename, $page ) { global $PCache; parent::write( $pagename, $page ); $text = $page['text']; $page = $PCache[$pagename]; if (empty( $page )) Abort("Page $pagename is blank?"); $page['text'] = $text; $this->writetop( $pagename, $page ); } function exists( $pagename ) { if (!$pagename) return false; $pagefile = $this->pagefile($pagename); $topfile = $this->pagetopfile($pagename); return ( ( $pagefile && file_exists($pagefile) ) || ( $topfile && file_exists($topfile) ) ); } function delete($pagename) { global $Now; $pagefile = $this->pagefile($pagename); @rename( $pagefile, "$pagefile,del-$Now" ); @unlink( $this->pagetopfile($pagename) ); } ## inherited: function ls($pats=NULL) }