display.php -
index
<?php
/* DISPLAY.PHP - Functions to display data. Most functions here are called by
a process we call RULES and more information can be found in RULES.PHP.
This code is okay, so there is not too much more work to be done:
clean up how error handling is done
make sure things fail silently
more "check for stuff and get out" if error
This code is the heart of THIS as it does all the data input handling
and all the HTML display -- hence the name. Everything goes through this
file (so to speak).
*/
setmodule();
if (!defined('CONFIGNODUMP') && debug()) // let ADMIN turn this off
register_shutdown_function('_displaydebug');
_usercode_global(); // see file USERCODE
/* displayhtml - display content/HTML mixed with PHP variables */
// Variable number of arguments; basically:
//
// $templ template name (index in assoc. array of templates)
// $name name (PHP variable for the data, include the '$')
// $data data referenced in the HTML template
//
// Example: displayhtml('comment','$record',$record);
//
// This way $record can be referenced by the HTML directly: {$record['title']}
function displayhtml() {
$args = func_get_args();
$templ = $args[0];
debug("'$templ'");
// set variable variable if there is one
if (isset($args[1])) { // assumes if 1 then 2
$val = str_replace('$','',$args[1]);
$$val = $args[2];
}
$html = html(); // data that can be referenced
$config = config(); // by the HTML
$arg = getvar('arg');
$text = htmltempl($templ);
if (!$text) {
if (config('shownotfounds'))
echo "template '$templ' not found";
return;
}
if (!config('shownotices'))
$e = error_reporting(E_ALL ^ E_NOTICE);
if (isset($val) && is_string($$val))
eval(_textset($$val,$val));
if (!isset($val)) {
$text = addcslashes($text,'"');
eval("\$text = \"$text\";");
}
$text = addcslashes($text,'"');
eval("print \"$text\";");
if (isset($e))
error_reporting($e);
}
/* displaymessage - display a message in place of a post */
function displaymessage($message, $class = 'entry') {
$html = html(); // data that can be referenced
$config = config(); // by the HTML
$arg = getvar('arg');
if (!config('shownotices'))
$e = error_reporting(E_ALL ^ E_NOTICE);
$data = "<!-- displaymessage -->\n<div class='$class'>$message</div>\n";
eval(_htmlshow('',$data));
if (isset($e))
error_reporting($e);
}
/* displayentries - display posts */
function displayentries() {
$ids = dblistrecords(); // array if IDs
if ($ids === FALSE) {
$e = "Could not read from '".dbtable()."'.";
if (debug()) $e = 'displayentries '.dberror();
displaymessage($e);
return;
}
sortrecords($ids); // config('sortfunc')
$arg = getvar('arg');
$start = (int)getvar('start');
if ($start < 0 || $start >= count($ids))
$start = 0;
$end = config('postsperpage');
$i = 0; $c = 0;
foreach ($ids as $id) {
if ($i++ < $start) continue;
$record = readrecord($id);
if (!$record) // should never happen but
continue; // never say never...
if (isset($record['draft'])) {
debug('draft skipped');
continue;
}
$titles[$record['id']] = substr($record['title'],0,60);
// record goes "out to User Agent" through this:
displayrecord($record,$id);
if (++$c == $end)
break;
}
if ($c == 0)
displaymessage('No posts.');
if ($c > 1 && htmltempl('quickies')) {
$html = html_elements($titles,'<option value="$1">$2</option>');
displayhtml('quickies','$OPTIONS',$html);
}
// this (if you have updated the code) is the "new" way of doing the MORE/PREV
// links; 'moreprev' is the template, and it references 'count', 'start' etc.
// which means a slight obfuscation but less code (4/15/13)
$start = $start - config('postsperpage');
html('count',$i);
html('start',$start);
html('more_vis',($i < count($ids)) ? 'visible' : 'hidden');
html('prev_vis',($start >= 0) ? 'visible' : 'hidden');
displayhtml('moreprev');
if (config('allowbasecomments'))
displaycomments();
}
/* displayentry - display a single entry (called when 'grok'ing) */
// see RULES.PHP for input validation
function displayentry() {
$id = getvar('id');
if ($id === '' || !dblistrecords($id)) {
displaymessage('Post not found');
return;
}
$record = readrecord($id);
displayrecord($record,$id);
if (config('allowcomments') && !isset($record['nocomments']))
displaycomments();
}
/* displaycomments - display comments if there are any */
// comments are associated with an entry; just display them all, we do not
// expect to be a site with thousands of comments! see config('maxmsgs')
// NOTE this used to be a "rule" (in RULES.INI) but then we "internalized"
// it by conditionally calling it here -- and 'id' is validated previously
function displaycomments() {
$entryid = getvar('id');
if ($entryid == '')
$entryid = 0; // root comments
debug("'$entryid'");
$ids = dblistcomments($entryid);
if ($ids === FALSE) // db query error
return;
if (!$ids && config('nocommentadd'))
return;
displayhtml('commentintro');
$msgcount = count($ids);
if ($msgcount) {
if (config('rsortcomments'))
$ids = array_reverse($ids);
foreach ($ids as $id) {
$record = dbreadcomment($id);
displayhtml('comment','$record',$record);
}
}
else
displayhtml('nocomment');
if (config('nocommentadd'))
return;
if ($msgcount >= config('maxmsgs'))
displayhtml('commentmax');
else
displaycommentform();
debug("msgcount '$msgcount'");
}
/* displaycommentform - displays 'commentadd' template */
// NOTE This code ALLOWS anyone to post a comment!
// NOTE We know that you probably will not like that!
// NOTE We are going to change this but probably not very soon.
function displaycommentform() {
$html = html();
$arg = getvar('arg');
$entryid = getvar('id');
$url = "?arg=$arg&id=$entryid&op=submit";
$code = 0; // used to be a user function
$from = cookie_get('from'); // until we found out it's
if ($from) { // simpler here
$code = 1;
$_POST['from'] = $from;
}
$form['fromlink'] = $html['fromlink'];
if (!config('visitorcode'))
$form['hidecode'] = 'hide';
if ($code) { // logged in if true
$form['hidecode'] = 'hide';
$form['hidefrom'] = 'hide';
$form['fromlink'] = str_replace('$1',$from,$html['frominlink']);
}
$form['url'] = $url;
$form['cancel'] = isset($_POST['button']) ? '' : 'disabled';
$form['submit'] = '';
if (config('mustpreview')) {
$form['submit'] = isset($_POST['button']) ? '' : 'disabled';
if (htmlgeterror('comment_message'))
$form['submit'] = 'disabled'; // reset if error
}
foreach ($_POST as $n => $v)
$_POST[$n] = htmlspecialchars($v);
displayhtml('commentadd','$form',$form);
}
/* displaysubmit - result of form to submit user comment */
// NOTE this function knows the following POST inputs:
//
// 'from', 'code', 'subj', 'body', 'button'
//
// and these GET inputs:
//
// 'arg, 'id'
//
// $arg and $id are validated elsewhere, the others are validated here
//
// (as long as $code is empty, as it should be if 'visitorcode' is undefined
// or zero, all of the "visitor" functions do nothing)
function displaysubmit() {
$arg = getvar('arg');
$entryid = (int)getvar('id'); // make sure "" is 0
$button = postvar('button');
if (!dblistrecords($entryid)) {
$entryid = 0;
$button = 'cancel';
}
if ($entryid == 0)
$url = "?arg=$arg";
else
$url = "?arg=$arg&op=grok&id=$entryid";
if ($button == 'cancel')
redirect($url);
$commentadd = array(
'from' => array('test' => '/[<>]/', 'length' => 32),
'subj' => 64,
'code' => 64,
'data' => config('postlimit')
);
// normally data is to be put in an INI file but this is a new way of doing
// things so we will wait to do that (looks nice here, actually!)
list($from,$subj,$code,$data) = postvars($commentadd); // POST.PHP
checkbadinput($data); // in ERROR.PHP
$vcode = visitor_validate($from, $code);
// now we offer up some feedback if any inputs are stupid inputs
if ($from == '-1' || $from == '0') // see note *
$error = htmlseterror('DATABAD');
if ($data == '') // since no elses
$error = htmlseterror('SUBMIT'); // these are order
// dependent
if ($vcode === FALSE)
$error = htmlseterror('FROM'); // and don't put elses
// because they're not
if ($vcode === NULL) // mutually exclusive
$error = htmlseterror('CODE');
if ($button == 'preview' || isset($error)) {
do_rules('preview'); // call another rule
exit; // (it's okay)
}
if ($button == 'submit' && !isset($error)) {
visitor_store($from,$code);
$record = _commentcreat($from,$subj,$data);
// note that comments, unlike posts, are stored in their converted form,
// which is weird... but posts are editable and comments are not
$r = dbwritecomment($entryid,$record);
// what can go wrong here? need to test...
$url .= "#$r";
}
redirect($url);
}
// * There are two odd things about the "from" input: We allow it to be empty
// but do not allow angle thingys ('<' and '>'). When it is empty we change it
// to 'nodody' (this happens somewhere deep down inside the code and we forget
// where it was written so long ago). When there are any anglys the post
// parser flags that as "bad" by making it "-1" (that happens in INC/POST.PHP
// and it was written just yesterday). When displaysubmit() sees 'from' as
// "-1" it refuses the post and issues a vague diagnostic. We then added a
// test to also fail for a name of "0" just to confuse things. (The user, not
// you.)
/* displaypreview - preview a comment */
function displaypreview() {
$from = postvar('from');
$subj = postvar('subj');
$data = postvar('data');
$record = _commentcreat($from,$subj,$data); // POST data
displayhtml('commentpreview');
displayhtml('comment','$record',$record);
}
/* displayrecord - display record data */
// $record entry to display
// ($id) for grok url
function displayrecord($record, $id = '') {
$op = getvar('op');
$arg = getvar('arg');
$single = ($op == 'grok');
if ($op == 'page')
$record['grok'] = "?arg=$arg&op=page&id=$id";
else
$record['grok'] = "?arg=$arg&op=grok&id=$id";
extract($record,EXTR_OVERWRITE|EXTR_PREFIX_ALL|EXTR_REFS,'r');
if (!$single && isset($r_more)) { // 'more' a truncated
$r_body = $r_more; // copy of 'body'
$record['murl'] = _morelink($r_grok);
}
unset($record['more']);
debug($record,config('debugtruncate'));
// this is where we are going to use an admin template if logged in as admin
if ($single)
$htfile = 'entry';
else
$htfile = 'entries';
// user code for this entry?
if (isset($r_usercode)) {
$codetag = $r_usercode;
_usercode($codetag,$record);
}
else
// or for all entries?
if (config('usercode')) {
$codetag = config('usercode');
_usercode($codetag,$record);
}
if (config('notranslate'))
$r_body = htmlentities($r_body);
if (!$single && config('allowcomments') && !isset($r_nogrok))
$record['curl'] = _commentlink($r_grok,$record);
// an admin thing:
if ($single && isset($_GET['html']))
$r_body = '<pre>'.htmlentities($r_body).'</pre>';
// goes out to UA here:
displayhtml($htfile,'$record',$record);
}
/* displaypage - display a "page" */
function displaypage() {
$id = getvar('id');
$arg = getvar('arg');
if (preg_match('/[^a-zA-Z0-9]+/',$id))
$id = '';
if ($id === '' || !dblistpages($id)) {
displaymessage('<b>Page not found.</b>');
return;
}
$record = readfilerecord($id);
$record['grok'] = "?arg=$arg&op=page&id=$id";
displayhtml('page','$record',$record);
}
/* displayhelp - display site help page */
// this is a "first draft"
function displayhelp() {
$arg = getvar('arg');
$topic = ($topic = getvar('h')) ? $topic : "index";
$help = "doc/help/$topic.html";
if (is_file($help))
eval(_htmlshow($help));
else {
echo "<div class='helptext'>";
echo "hmm... the help file for '$topic' is missing";
echo "</div>";
}
}
/* displayfile - display a text file */
// right now we expect only to be used for plain text files no matter the
// extension; should we do images and stuff?
// this was experimental and probably will go away...
function displayfile() {
$srcdir = config('srcdir');
$file = getvar('file');
if (strpos($file,'/') !== FALSE || !is_file($srcdir.$file)) {
echo("file '$file' not found");
return;
}
header('Content-type: text/plain');
readfile($srcdir.$file);
}
/* login - login form handler */
function login() {
$post = 'name,code,redirect';
eval($GLOBALS['POST']); // use our new PHP macro!
$visit = visitor_okay($name,$code);
// 'It is recommended that you avoid "stacking" ternary expressions.'
// -- PHP.NET
$r = ($visit === FALSE) ? "not found" : (($visit === NULL) ? "bad code" : (($visit === "") ? "missing" : "okay!"));
debug("visitor: '$name','$code' = $r");
if ($visit)
cookie_set('from',$name);
redirect('?'.$redirect);
// NOTE when there is a redirect like here, any debug output is lost; that's
// why there is the "noredirect" configuration setting
// NOTE ALSO that any output prevents the cookie from being set and possibly
// even the redirect!
}
/* logout - forget visitor information */
// we actually do not need this as it can now be done directly as a rule;
// see [logout] in RULES.INI
function logout() {
$post = 'redirect';
eval($GLOBALS['POST']);
cookie_unset('from');
redirect('?'.$redirect);
}
/* _htmlshow - read a template file or buffer */
function _htmlshow($file, $html = NULL) {
if (!$html)
if (!($html = file_get_contents($file)))
$html = "$file notfound";
$html = addcslashes($html,'"');
return "print \"$html\";";
}
function _textset($text, $name = 'text') {
$text = addcslashes($text,'"');
return "\$$name = \"$text\";";
}
/* _usercode - use external code to post-process a record */
// This applies code to the body of a record. The code is stored
// in a file named "usercode.ini", which should have a section
// (e.g. [tagname]) for straight PHP code (there can be multiple
// sections). The entry should then have a header like:
//
// usercode: codeid
//
// Before the usercode is called the record body is copied into
// an array named '$user_data' so that the usercode can have a
// working copy of the data. (The body is exploded by newlines.)
//
// The usercode can modify the '$record' directly.
function _usercode($codeid, &$record) {
debug("'$codeid'");
if (strstr($codeid,',')) {
$user_args = splitntrim($codeid);
$codeid = $user_args[0];
}
else
$user_args[0] = $codeid;
$uc = usercode($codeid); // get the usercode
if ($uc === FALSE) {
$umsg = "usercode:'$codeid' not found";
$record['body'] .= "<div class='note'>$umsg</div>";
return;
}
$user_text = ""; // text placeholder
$user_body = $record['body']; // working copies
$user_data = explode(THIS_EOL,$record['body']);
$r = eval($uc); // and run it
/* PHP.NET: "If there is a parse error in the evaluated code, eval() returns
FALSE and execution of the following code continues normally. It is not
possible to catch a parse error in eval() using set_error_handler()." */
if ($r == 1) // okay so use text
$record['body'] = $user_text;
else
if ($r === FALSE) {
$umsg = "usercode:'$codeid' parse error";
$record['body'] .= "<div class='note'>$umsg</div>";
}
// this is not ideal code: first, it uses 'eval' and although harmless,
// it might be slightly more efficient to use user created functions
// as done for BLOCKS.INI (which would be easy to do); second, it
// bypasses all other conversions (and translations)! which might not
// be so good afterall... but that can be changed too
}
/* _usercode_global - apply any '[global]' usercode code */
function _usercode_global() {
if (config('admin'))
return;
$r = '';
$uc = usercode('global');
if ($uc) $r = eval($uc);
debug("'$r'");
}
/* _referervalid - return referer is server */
function _referervalid() {
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
return 0;
$server = "/^http:\/\/{$_SERVER['HTTP_HOST']}/";
$referer = $_SERVER['HTTP_REFERER'];
return preg_match($server,$referer);
}
/* _commentcreat - convert POST data to comment "record" */
function _commentcreat($from, $title, $body) {
$date = datestrcmt();
$record['title'] = $title;
$record['from'] = $from;
$record['date'] = $date;
$record['body'] = $body;
// we moved body filtering into the convert function
foreach ($record as $n => $v) {
if ($n == 'body')
$record[$n] = convert_user($v);
else
$record[$n] = strip_tags($v);
}
if (config('loguserdata'))
recorduserdata($record); // modifies $record
if ($record['from'] == "") $record['from'] = "nobody";
if ($record['title'] == "") $record['title'] = "nothing";
return $record;
}
/* _commentlink - form the "comments" link (curl) */
function _commentlink($url, $record) {
$n = 0;
if ($record['id'])
$n = dbcomment('count',$record['id']);
$link = ($t = html('commentstext')) ? $t : "comments ($n)";
$link = str_replace('$N',$n,$link);
$link = "<a href='$url#comments'>$link</a>";
if (($linkwrap = html('commentslink')) != "")
$link = str_replace('$1',$link,$linkwrap);
return $link;
}
function _morelink($url) {
$more = ($m = html('moretext')) ? $m : "there is more";
$link = "<a href='$url'>$more</a>";
return $link;
}
/* _displaydebug - print_r all the arrays and stuff */
// this is a registered function so gets called last
// this can dump EVERYTHING, that is all data generated and used by this code
// no sensitive data is store in accessible memory
// see file DEBUGGING
// see file CONFIG
function _displaydebug() {
if (defined('CONFIGNODUMP')) return;
if (($d = debug()) == 0 || $d < 0) return;
// if debug set to display in HTML comments don't do two things:
// do not wrap output in the "<pre></pre>" tags
// do not htmlentitie-ize them -- but must eat '-->' and '--' !
if ($d < 100)
print "\n<div style='background: white; color: black;'><pre>";
else
print "<!--\n";
ob_start(($d < 100) ? 'obentities' : 'obcomments');
$ad = ($d > 100) ? $d - 100 : $d;
if ($ad == 2) {
dump($_GET,"_GET: ",-1);
dump($_POST,"_POST: ",-1);
}
if ($ad == 3) {
dump(get_defined_constants(true),"CONSTANTS ",-1);
}
if ($ad == 4) {
dump($GLOBALS,"GLOBALS ",-1);
}
if ($ad == 5) {
dumpusercode();
}
if ($ad == 9) {
dump($_GET,"_GET: ",-1);
dump($_POST,"_POST: ",-1);
dump(config(),"CONFIG: ",-1);
dump(html(),"HTML: ",-1);
if (function_exists('rules'))
dump(rules(),"RULES: ",-1);
}
ob_end_flush();
print ($d < 100) ? "</pre></div>" : THIS_EOL."-->";
}
?>