*
* 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-02-11';
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;
function PageTopStore( $d='$WorkDir/$FullName', $t='wikitop.d/{$FullName}', $w=0, $a=NULL ) {
$this->dirfmt = $d;
$this->topfmt = $t;
$this->iswrite = $w;
$this->attr = (array)$a;
$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 = '[PageTopStore]';
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 );
foreach( $page as $k => $v ) if (
( $k > '' ) && ( $k[0] != '=' ) &&
( $k != 'version' ) && ( $k != 'text' ) && ( $k != 'newline' ) &&
( strpos($k,':') == FALSE )
) {
$x = str_replace( $r0, $r1, "$k=$v" ) . "\n";
$st = $st && fputs( $fp, $x );
$tz += strlen($x);
}
$st = $st && fputs( $fp, "\n" . $page['text'] . "\n" );
$tz += 2 + strlen($page['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'] = trim(stream_get_contents( $ft ));
$page['text'] = stream_get_contents( $ft );
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)
}