* copyright : (C) 2001-2022 Advanced Internet Designs Inc.
* email : forum@prohost.org
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; version 2 of the License.
require('./GLOBALS.php');
fud_use('fpdf.inc', true);
class fud_pdf extends FPDF
/** Override Cell() function to render special characters (FPDF doesn't support UTF-8).
* Details at http://fudforum.org/forum/index.php?t=msg&goto=167344
function Cell($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='')
if (extension_loaded('iconv')) {
$txt = iconv('utf-8', 'cp1252', $txt);
parent::Cell($w, $h, $txt, $border, $ln, $align, $fill, $link);
function begin_page($title)
function input_text($text)
$this->SetFont('helvetica', '', 12);
$this->Line($this->lMargin, $this->y, ($this->w - $this->rMargin), $this->y);
function add_link($url, $caption=0)
$this->SetTextColor(0,0,255);
$this->Write(5, $caption ? $caption : $url, $url);
function add_attacments($attch, $private=0)
$this->SetFont('courier', '', 16);
$this->Write(5, 'File Attachments');
$this->SetFont('', '', 14);
$this->Write(5, ++$i .') ');
$this->add_link($GLOBALS['WWW_ROOT'] .'index.php?t=getfile&id='. $a['id'] . ($private ? '&private=1' : ''), $a['name']);
$this->Write(5, ', downloaded '. $a['nd'] .' times');
// GIF, PNG and JPG images can be embedded.
if (extension_loaded('gd') && preg_match('/\.gif$/i', $a['name'])) {
$this->Image($GLOBALS['WWW_ROOT'] .'index.php?t=getfile&id='. $a['id'] . ($private ? '&private=1' : ''), null, null, 0, 0, 'GIF');
} elseif (preg_match('/\.png$/i', $a['name'])) {
$this->Image($GLOBALS['WWW_ROOT'] .'index.php?t=getfile&id='. $a['id'] . ($private ? '&private=1' : ''), null, null, 0, 0, 'PNG');
} elseif (preg_match('/\.(jpg|jpeg)$/i', $a['name'])) {
$this->Image($GLOBALS['WWW_ROOT'] .'index.php?t=getfile&id='. $a['id'] . ($private ? '&private=1' : ''), null, null, 0, 0, 'JPEG');
function add_poll($name, $opts, $ttl_votes)
$this->SetFont('courier', '', 16);
$this->SetFont('', '', 14);
$this->Write(5, '(total votes: '. $ttl_votes .')');
$p1 = ($this->w - $this->rMargin - $this->lMargin) * 0.6 / 100;
// Avoid /0 warnings and safe to do, since we'd be multiplying 0 since there are no votes,
$this->SetFont('helvetica', '', 14);
$this->SetFillColor(52, 146, 40);
$this->Cell((!$o['votes'] ? 1 : $p1 * (($o['votes'] / $ttl_votes) * 100)), 5, $o['name'] ."\t\t". $o['votes'] .'/('.round(($o['votes'] / $ttl_votes) * 100).'%)', 1, 0, '', 1);
function message_header($subject, $author, $date, $id, $th)
$this->Rect($this->lMargin, $this->y, (int)($this->w - $this->lMargin - $this->lMargin), 1, 'F');
$this->SetFont('helvetica', '', 14);
$this->Bookmark($subject, 1);
$this->Write(5, 'Subject: '. $subject);
$this->Write(5, 'Posted by ');
$this->add_link($GLOBALS['WWW_ROOT'] .'index.php?t=usrinfo&id='. $author[0], $author[1]);
$this->Write(5, ' on '. gmdate('D, d M Y H:i:s \G\M\T', $date));
$this->SetFont('helvetica', '', 10);
$this->add_link($GLOBALS['WWW_ROOT'] .'index.php?t=rview&th='. $th .'&goto='. $id .'#msg_'. $id, 'View Forum Message');
$this->add_link($GLOBALS['WWW_ROOT'] .'index.php?t=post&reply_to='. $id, 'Reply to Message');
$this->Rect($this->lMargin, $this->y, (int)($this->w - $this->lMargin - $this->lMargin), 1, 'F');
$this->SetFont('courier', '', 8);
$this->Write(5, 'Page '. $this->page .' of {fnb} ---- Generated from ');
$this->add_link($GLOBALS['WWW_ROOT'] .'index.php', $GLOBALS['FORUM_TITLE']);
// $this->Write(5, ' by FUDforum '. $GLOBALS['FORUM_VERSION']);
function Bookmark($txt, $level=0)
$this->outlines[] = array('t' => $txt, 'l' => $level, 'y' => $this->y, 'p' => $this->page);
if (empty($this->outlines)) {
$nb = count($this->outlines);
foreach ($this->outlines as $i => $o) {
$parent = $lru[$o['l']-1];
// Set parent and last pointers.
$this->outlines[$i]['parent'] = $parent;
$this->outlines[$parent]['last'] = $i;
// Level increasing: set first pointer.
$this->outlines[$parent]['first'] = $i;
$this->outlines[$i]['parent'] = $nb;
if($o['l'] <= $level && $i > 0) {
// Set prev and next pointers.
$this->outlines[$prev]['next'] = $i;
$this->outlines[$i]['prev'] = $prev;
foreach($this->outlines as $i => $o) {
$this->_out('<</Title '. $this->_textstring($o['t']));
$this->_out('/Parent '. ($n+$o['parent']).' 0 R');
$this->_out('/Prev '. ($n+$o['prev']).' 0 R');
$this->_out('/Next '. ($n+$o['next']).' 0 R');
$this->_out('/First '. ($n+$o['first']).' 0 R');
$this->_out('/Last '. ($n+$o['last']).' 0 R');
$this->_out(sprintf('/Dest [%d 0 R /XYZ 0 %.2f null]', 1+2*$o['p'], ($this->h-$o['y'])*$this->k));
$this->_out('/Count 0>>');
$this->OutlineRoot = $this->n;
$this->_out('<</Type /Outlines /First '. $n .' 0 R');
$this->_out('/Last '. ($n+$lru[0]) .' 0 R>>');
/* Before we go on, we need to do some very basic activation checks. */
if (!($FUD_OPT_1 & 1)) { // FORUM_ENABLED
/* This potentially can be a longer form to generate. */
@set_time_limit($PDF_MAX_CPU);
# define('fud_query_stats', 1);
class db { public static $db, $slave; }
// Use MYSQLI_REPORT_OFF so we can check error codes manually.
$driver = new mysqli_driver();
$driver->report_mode = MYSQLI_REPORT_OFF;
if (substr($GLOBALS['DBHOST'], 0, 1) == ':') { // Socket connection.
$socket = substr($GLOBALS['DBHOST'], 1);
$GLOBALS['DBHOST'] = 'localhost';
if ($GLOBALS['FUD_OPT_1'] & 256 && $socket == NULL && version_compare(PHP_VERSION, '5.3.0', '>=')) { // Enable pconnect for PHP 5.3+.
$GLOBALS['DBHOST'] = 'p:'. $GLOBALS['DBHOST'];
db::$db = new mysqli($GLOBALS['DBHOST'], $GLOBALS['DBHOST_USER'], $GLOBALS['DBHOST_PASSWORD'], $GLOBALS['DBHOST_DBNAME'], NULL, $socket);
if (mysqli_connect_errno()) {
fud_sql_error_handler('Failed to establish database connection', 'MySQLi says: '. mysqli_connect_error(), mysqli_connect_errno(), '');
db::$db->set_charset('utf8');
/* Connect to slave, if specified. */
if (!empty($GLOBALS['DBHOST_SLAVE_HOST']) && !$GLOBALS['is_post']) {
db::$slave = new mysqli($GLOBALS['DBHOST'], $GLOBALS['DBHOST_USER'], $GLOBALS['DBHOST_PASSWORD'], $GLOBALS['DBHOST_DBNAME'], NULL, $socket);
if (mysqli_connect_errno()) {
fud_logerror('Unable to init SlaveDB, fallback to MasterDB: '. mysqli_connect_error(), 'sql_errors');
db::$db->set_charset('utf8');
define('__dbtype__', 'mysql');
if (!defined('__FUD_SQL_VERSION__')) {
$ver = q_singleval('SELECT VERSION()');
define('__FUD_SQL_VERSION__', $ver);
return __FUD_SQL_VERSION__;
function db_lock($tables)
if (!empty($GLOBALS['__DB_INC_INTERNALS__']['db_locked'])) {
fud_sql_error_handler('Recursive Lock', 'internal', 'internal', db_version());
q('LOCK TABLES '. $tables);
$GLOBALS['__DB_INC_INTERNALS__']['db_locked'] = 1;
if (empty($GLOBALS['__DB_INC_INTERNALS__']['db_locked'])) {
unset($GLOBALS['__DB_INC_INTERNALS__']['db_locked']);
fud_sql_error_handler('DB_UNLOCK: no previous lock established', 'internal', 'internal', db_version());
if (--$GLOBALS['__DB_INC_INTERNALS__']['db_locked'] < 0) {
unset($GLOBALS['__DB_INC_INTERNALS__']['db_locked']);
fud_sql_error_handler('DB_UNLOCK: unlock overcalled', 'internal', 'internal', db_version());
unset($GLOBALS['__DB_INC_INTERNALS__']['db_locked']);
return isset($GLOBALS['__DB_INC_INTERNALS__']['db_locked']);
return db::$db->affected_rows;
if (!defined('fud_query_stats')) {
// Assume master DB, route SELECT's to slave DB.
// Force master if DB is locked (in transaction) or 'SELECT /* USE MASTER */'.
if (!empty(db::$slave) && !db_locked() && !strncasecmp($query, 'SELECT', 6) && strncasecmp($query, 'SELECT /* USE MASTER */', 23)) {
fud_sql_error_handler($query, $db->error, $db->errno, db_version());
if (!isset($GLOBALS['__DB_INC_INTERNALS__']['query_count'])) {
$GLOBALS['__DB_INC_INTERNALS__']['query_count'] = 1;
++$GLOBALS['__DB_INC_INTERNALS__']['query_count'];
if (!isset($GLOBALS['__DB_INC_INTERNALS__']['total_sql_time'])) {
$GLOBALS['__DB_INC_INTERNALS__']['total_sql_time'] = 0;
// Assume master DB, route SELECT's to slave DB.
// Force master if DB is locked (in transaction) or 'SELECT /* USE MASTER */'.
if (!empty(db::$slave) && !db_locked() && !strncasecmp($query, 'SELECT', 6) && strncasecmp($query, 'SELECT /* USE MASTER */', 23)) {
$result = $db->query($query);
fud_sql_error_handler($query, $db->error, $db->errno, db_version());
$GLOBALS['__DB_INC_INTERNALS__']['last_time'] = ($e - $s);
$GLOBALS['__DB_INC_INTERNALS__']['total_sql_time'] += $GLOBALS['__DB_INC_INTERNALS__']['last_time'];
echo '<hr><b>Query #'. $GLOBALS['__DB_INC_INTERNALS__']['query_count'] .'</b><small>';
echo ': time taken: <i>'. number_format($GLOBALS['__DB_INC_INTERNALS__']['last_time'], 4) .'</i>';
echo ', affected rows: <i>'. db_affected() .'</i>';
echo ', total sql time: <i>'. number_format($GLOBALS['__DB_INC_INTERNALS__']['total_sql_time'], 4) .'</i>';
echo '<pre>'. preg_replace('!\s+!', ' ', htmlspecialchars($query)) .'</pre></small>';
function db_rowobj($result)
return $result->fetch_object();
function db_rowarr($result)
return $result->fetch_row();
function q_singleval($query)
if (($result = $r->fetch_row()) !== false && isset($result)) {
return isset($result) ? $result[0] : '';
function q_limit($query, $limit, $off=0)
return $query .' LIMIT '. $limit .' OFFSET '. $off;
// MySQL badly breaks the SQL standard by redefining || to mean OR.
return 'CONCAT('. implode(',', $tmp) .')';
q('SET @seq=0'); // For simulating rownum.
function q_bitand($fieldLeft, $fieldRight) {
return $fieldLeft .' & '. $fieldRight;
function q_bitor($fieldLeft, $fieldRight) {
return '('. $fieldLeft .' | '. $fieldRight .')';
function q_bitnot($bitField) {
return $r->fetch_object();
return db::$db->insert_id;
function db_arr_assoc($q)
return $r->fetch_array(MYSQLI_ASSOC);
function db_fetch_array($r)
return is_object($r) ? $r->fetch_array(MYSQLI_ASSOC) : null;
function db_li($q, &$ef, $li=0)
return ($li ? db::$db->insert_id : $r);
if (db::$db->errno == 1062) {
$ef = ltrim(strrchr(db::$db->error, ' '));
fud_sql_error_handler($q, db::$db->error, db::$db->errno, db_version());
function ins_m($tbl, $flds, $types, $vals)
q('INSERT IGNORE INTO '. $tbl .' ('. $flds .') VALUES ('. implode('),(', $vals) .')');
while ($r = $c->fetch_row()) {