Forum How do I...?

How to Integrate vBulletin with Prince

patpat
I've done a vBulletin-Prince integration after I've got frustrated trying to get running the ones available at http://www.vbulletin.org/

Feel free to try it, no warranties, no support but it works on my site with a 3.6.8. I do not think there's going to be any problem with other versions..

You have to install the following files into the respective directories on your Vbulletin installation

forumRoot/threadtopdf.php
<?php

// ####################### SET PHP ENVIRONMENT ###########################
error_reporting(E_ALL & ~E_NOTICE);

// #################### DEFINE IMPORTANT CONSTANTS #######################
define('THIS_SCRIPT', 'printthread');

// ################### PRE-CACHE TEMPLATES AND DATA ######################
// get special phrase groups
$phrasegroups = array('showthread', 'postbit');

// get special data templates from the datastore
$specialtemplates = array(
	'smiliecache',
	'bbcodecache'
);

// pre-cache templates used by all actions
$globaltemplates = array(
	'printthread',
	'printthreadbit',
	'printthreadbit_ignore',
	'bbcode_code_printable',
	'bbcode_html_printable',
	'bbcode_php_printable',
	'bbcode_quote_printable',
);


// pre-cache templates used by specific actions
$actiontemplates = array();

// ######################### REQUIRE BACK-END ############################require_once('./global.php');
require_once('./global.php');
require_once(DIR . '/includes/class_bbcode.php');
require_once(DIR . '/includes/functions_bigthree.php');
require_once(DIR . '/includes/class_threadtopdf.php');
require_once(DIR . '/includes/class_prince.php');
// #######################################################################
// ######################## START MAIN SCRIPT ############################
// #######################################################################


///////////////////////////////////////////////////////////////////////////////////////////
/// Pat function for trapping & Debugging !!
function trap ( $var = NULL, $exit = 0 )
  {
  if ( isset ( $var ) )
    {
    echo '<pre>';
    print_r ( $var );
    echo '</pre>';
    if ( $exit ) exit;
    }
  else
    {
    echo '<span style="font: 14px bold 
                sans-serif; color: red">***</span>';
    }
  }
///////////////////////////////////////////////////////////////////////////////////////////


 
$vbulletin->input->clean_array_gpc('r', array(
	'perpage'	=> TYPE_UINT,
	'pagenumber'=> TYPE_UINT
));

// oldest first or newest first
if ($vbulletin->userinfo['postorder'] == 0)
{
	$postorder = '';
}
else
{
	$postorder = 'DESC';
}

if ($vbulletin->options['wordwrap'])
{
	$threadinfo['title'] = fetch_word_wrapped_string($threadinfo['title']);
}

if (!$threadinfo['threadid'] OR (!$threadinfo['visible'] AND !can_moderate($threadinfo['forumid'], 'canmoderateposts')) OR $threadinfo['isdeleted'] OR (in_coventry($threadinfo['postuserid']) AND !can_moderate($threadinfo['forumid'])))
{
	eval(standard_error(fetch_error('invalidid', $vbphrase['thread'], $vbulletin->options['contactuslink'])));
}

$forumperms = fetch_permissions($threadinfo['forumid']);
if (!($forumperms & $vbulletin->bf_ugp_forumpermissions['canview']) OR !($forumperms & $vbulletin->bf_ugp_forumpermissions['canviewthreads']))
{
	print_no_permission();
}
if (!($forumperms & $vbulletin->bf_ugp_forumpermissions['canviewothers']) AND ($threadinfo['postuserid'] != $vbulletin->userinfo['userid'] OR $vbulletin->userinfo['userid'] == 0))
{
	print_no_permission();
}

if ($threadinfo['open'] == 10)
{
	exec_header_redirect('threadtopdf.php?' . $vbulletin->session->vars['sessionurl_js'] . "t=$threadinfo[pollid]");
}

// check if there is a forum password and if so, ensure the user has it set
verify_forum_password($foruminfo['forumid'], $foruminfo['password']);

if (!isset($_GET['pdf']))
{
	// split thread over pages if necessary
	$countposts = $db->query_first_slave("
		SELECT COUNT(*) AS total
		FROM " . TABLE_PREFIX . "post AS post
		WHERE threadid=$threadinfo[threadid] AND visible=1
	");
	$totalposts = $countposts['total'];

	$vbulletin->GPC['perpage'] = sanitize_maxposts($totalposts);
	$maxperpage = sanitize_maxposts(-1);

	if ($vbulletin->GPC['pagenumber'] < 1)
	{
		$vbulletin->GPC['pagenumber'] = 1;
	}

	$startat = ($vbulletin->GPC['pagenumber'] - 1) * $vbulletin->GPC['perpage'];

	$pagenav = construct_page_nav($vbulletin->GPC['pagenumber'], $vbulletin->GPC['perpage'], $totalposts, 'threadtopdf.php?' . $vbulletin->session->vars['sessionurl'] . "t=$threadinfo[threadid]", '&amp;pp=' . $vbulletin->GPC['perpage']);
	// end page splitter

	$bbcode_parser =& new vB_BbCodeParser($vbulletin, fetch_tag_list());
	$bbcode_parser->printable = true;

	$ignore = array();
	if (trim($vbulletin->userinfo['ignorelist']))
	{
		$ignorelist = preg_split('/( )+/', trim($vbulletin->userinfo['ignorelist']), -1, PREG_SPLIT_NO_EMPTY);
		foreach ($ignorelist AS $ignoreuserid)
		{
			$ignore["$ignoreuserid"] = 1;
		}
	}

	$posts = $db->query_read_slave("
		SELECT post.*,post.username AS postusername,user.username
		FROM " . TABLE_PREFIX . "post AS post
		LEFT JOIN " . TABLE_PREFIX . "user AS user ON(user.userid = post.userid)
		WHERE post.threadid=$threadid AND post.visible=1
		ORDER BY dateline $postorder
		LIMIT $startat, " . $vbulletin->GPC['perpage'] . "
	");

	$postbits = '';
	while ($post = $db->fetch_array($posts))
	{
		// hide users in Coventry from non-staff members
		if ($tachyuser = in_coventry($post['userid']) AND !can_moderate($threadinfo['forumid']))
		{
			continue;
		}

		if ($tachyuser)
		{
			$show['adminignore'] = true;
			$maintemplatename = 'printthreadbit_ignore';
		}
		else if ($ignore["$post[userid]"])
		{
			$show['adminignore'] = false;
			$maintemplatename = 'printthreadbit_ignore';
		}
		else
		{
			$maintemplatename = 'printthreadbit';
		}

		$post['postdate'] = vbdate($vbulletin->options['dateformat'], $post['dateline']);
		$post['posttime'] = vbdate($vbulletin->options['timeformat'], $post['dateline']);

		if ($vbulletin->options['wordwrap'])
		{
			$post['title'] = fetch_word_wrapped_string($post['title']);
		}

		if (!$post['userid'])
		{
			$post['username'] = $post['postusername'];
		}

		$post['message'] = $bbcode_parser->parse($post['pagetext'], $foruminfo['forumid'], false);

		eval('$postbits .= "' . fetch_template($maintemplatename) . '";');

	}

	eval('print_output("' . fetch_template('printthread') . '");');
}
else         // if PDF=1 
{
	$linkToPDFFull = $linkToPDF = tempnam(DIR, 'thread' . $threadid);
	unlink($linkToPDFFull);

	$linkToPDFFull .= '.pdf';                      // it has the complete path and name of the temp PDF file
	$linkToPDF .= '.pdf';
	$linkToPDF = basename($linkToPDF);             //it has the base name of the pdf file


    $htmlFile = $vbulletin->options['bburl'] . '/printthread.php?t=' . $threadid;
    $cssFile =  $vbulletin->options['bburl'] . '/threadtopdf.css';
//--trap($htmlFile);     
//--trap($cssFile); 
//--trap($linkToPDFFull); 
//--trap($linkToPDF); 
//--trap(NULL,1);
    
    $pdf = new Prince('/opt/prince/bin/prince');
//--    trap(); 
    $pdf->setHTML(TRUE);
//--    trap(); 
    $pdf->addStyleSheet($cssFile);
//--    trap($pdf);

//--   trap();  
   $msgs = array();
   $result = $pdf->convert_file_to_file($htmlFile, $linkToPDFFull,$msgs);
//--   trap($msgs);
//--  trap($result);   
                 
     
    if ($result == FALSE )
    {        
        die('There was an error on the conversion');
    }


	if (isset($HTTP_SERVER_VARS['HTTP_USER_AGENT']) AND strpos($HTTP_SERVER_VARS['HTTP_USER_AGENT'], 'MSIE'))
	{
		Header('Content-Type: application/force-download');
	}
	else
	{
		Header('Content-Type: application/octet-stream');
	}

	

   Header('Content-disposition: attachment; filename="Thread' . $threadid . '.pdf');
   readfile($linkToPDFFull);
	unlink($linkToPDFFull);
}

?>


forumRoot/threadtopdf.css
@page {

margin: 45pt 45pt 45pt 45pt ;
size: US-Letter ;
    @top {
	content: "www.XXXYYYZZZ.com";
        font-size: 10pt;
        font-family:sans-serif, Verdana;            
               }

    @bottom {
	content: "Page " counter(page) " of " counter(pages);
        font-size: 10pt;
        font-family:sans-serif, Verdana;            

    }
}



td, p, li, div
{
	font: 10pt verdana, geneva, lucida, arial, helvetica, sans-serif;
}
.smallfont
{
	font-size: 11px;
}
.tborder
{
	border: 1px solid #808080;
}
.thead
{
	background-color: #EEEEEE;
}
.page
{
	background-color: #FFFFFF;
	color: #000000;
}



forumRoot/includes/class_prince.php
<?php

// Prince - PHP interface
// Copyright 2005-2008 YesLogic Pty. Ltd.
// http://www.princexml.com

class Prince
{
    private $exePath;
    private $styleSheets;
    private $isHTML;
    private $baseURL;
    private $doXInclude;
    private $httpUser;
    private $httpPassword;
    private $logFile;
    private $embedFonts;
    private $compress;
    private $encrypt;
    private $encryptInfo;

    public function __construct($exePath)
    {
	$this->exePath = $exePath;
	$this->styleSheets = '';
	$this->isHTML = false;
	$this->baseURL = '';
	$this->doXInclude = true;
	$this->httpUser = '';
	$this->httpPassword = '';
	$this->logFile = '';
	$this->embedFonts = true;
	$this->compress = true;
	$this->encrypt = false;
	$this->encryptInfo = '';
    }

    // Add a CSS style sheet that will be applied to each document.
    // cssPath: The filename of the CSS style sheet.
    public function addStyleSheet($cssPath)
    {
	$this->styleSheets .= '-s "' . $cssPath . '" ';
    }

    // Clear all of the CSS style sheets.
    public function clearStyleSheets()
    {
	$this->styleSheets = '';
    }

    // Specify whether documents should be parsed as HTML or XML/XHTML.
    // html: True if all documents should be treated as HTML.
    public function setHTML($html)
    {
	$this->isHTML = $html;
    }

    // Specify a file that Prince should use to log error/warning messages.
    // logFile: The filename that Prince should use to log error/warning
    //	    messages, or '' to disable logging.
    public function setLog($logFile)
    {
	$this->logFile = $logFile;
    }

    // Specify the base URL of the input document.
    // baseURL: The base URL or path of the input document, or ''.
    public function setBaseURL($baseURL)
    {
	$this->baseURL = $baseURL;
    }

    // Specify whether XML Inclusions (XInclude) processing should be applied
    // to input documents. XInclude processing will be performed by default
    // unless explicitly disabled.
    // xinclude: False to disable XInclude processing.
    public function setXInclude($xinclude)
    {
	$this->doXInclude = $xinclude;
    }

    // Specify a username to use when fetching remote resources over HTTP.
    // user: The username to use for basic HTTP authentication.
    public function setHttpUser($user)
    {
	$this->httpUser = $user;
    }
    
    // Specify a password to use when fetching remote resources over HTTP.
    // password: The password to use for basic HTTP authentication.
    public function setHttpPassword($password)
    {
	$this->httpPassword = $password;
    }

    // Specify whether fonts should be embedded in the output PDF file. Fonts
    // will be embedded by default unless explicitly disabled.
    // embedFonts: False to disable PDF font embedding.
    public function setEmbedFonts($embedFonts)
    {
	$this->embedFonts = $embedFonts;
    }

    // Specify whether compression should be applied to the output PDF file.
    // Compression will be applied by default unless explicitly disabled.
    // compress: False to disable PDF compression.
    public function setCompress($compress)
    {
	$this->compress = $compress;
    }

    // Specify whether encryption should be applied to the output PDF file.
    // Encryption will not be applied by default unless explicitly enabled.
    // encrypt: True to enable PDF encryption.
    public function setEncrypt($encrypt)
    {
	$this->encrypt = $encrypt;
    }

    // Set the parameters used for PDF encryption. Calling this method will
    // also enable PDF encryption, equivalent to calling setEncrypt(true).
    // keyBits: The size of the encryption key in bits (must be 40 or 128).
    // userPassword: The user password for the PDF file.
    // ownerPassword: The owner password for the PDF file.
    // disallowPrint: True to disallow printing of the PDF file.
    // disallowModify: True to disallow modification of the PDF file.
    // disallowCopy: True to disallow copying from the PDF file.
    // disallowAnnotate: True to disallow annotation of the PDF file.
    public function setEncryptInfo($keyBits,
				   $userPassword,
				   $ownerPassword,
				   $disallowPrint = false,
				   $disallowModify = false,
				   $disallowCopy = false,
				   $disallowAnnotate = false)
    {
	if ($keyBits != 40 && $keyBits != 128)
	{
	    throw new Exception("Invalid value for keyBits: $keyBits" .
		" (must be 40 or 128)");
	}

	$this->encrypt = true;

        $this->encryptInfo =
		' --key-bits ' . $keyBits .
                ' --user-password="' . $userPassword .
                '" --owner-password="' . $ownerPassword . '" ';

        if ($disallowPrint)
	{
            $this->encryptInfo .= '--disallow-print ';
	}
	    
        if ($disallowModify)
	{
            $this->encryptInfo .= '--disallow-modify ';
	}
	    
        if ($disallowCopy)
	{
            $this->encryptInfo .= '--disallow-copy ';
	}
	    
        if ($disallowAnnotate)
	{
            $this->encryptInfo .= '--disallow-annotate ';
	}
    }

    // Convert an XML or HTML file to a PDF file.
    // The name of the output PDF file will be the same as the name of the
    // input file but with an extension of ".pdf".
    // xmlPath: The filename of the input XML or HTML document.
    // msgs: An optional array in which to return error and warning messages.
    // Returns true if a PDF file was generated successfully.
    public function convert_file($xmlPath, &$msgs = array())
    {
	$pathAndArgs = $this->getCommandLine();
	$pathAndArgs .= '"' . $xmlPath . '"';
	    
	return $this->convert_internal_file_to_file($pathAndArgs, $msgs);
    }
    
    // Convert an XML or HTML file to a PDF file.
    // xmlPath: The filename of the input XML or HTML document.
    // pdfPath: The filename of the output PDF file.
    // msgs: An optional array in which to return error and warning messages.
    // Returns true if a PDF file was generated successfully.
    public function convert_file_to_file($xmlPath, $pdfPath, &$msgs = array())
    {
	$pathAndArgs = $this->getCommandLine();
	$pathAndArgs .= '"' . $xmlPath . '" "' . $pdfPath . '"';
	    
        return $this->convert_internal_file_to_file($pathAndArgs, $msgs);
    }
    
    // Convert an XML or HTML string to a PDF file, which will be passed
    // through to the output of the current PHP page.
    // xmlString: A string containing an XML or HTML document.
    // Returns true if a PDF file was generated successfully.
    public function convert_string_to_passthru($xmlString)
    {
	$pathAndArgs = $this->getCommandLine();
	$pathAndArgs .= '--silent -';
	    
        return $this->convert_internal_string_to_passthru($pathAndArgs, $xmlString);
    }
    
    // Convert an XML or HTML string to a PDF file.
    // xmlString: A string containing an XML or HTML document.
    // pdfPath: The filename of the output PDF file.
    // msgs: An optional array in which to return error and warning messages.
    // Returns true if a PDF file was generated successfully.
    public function convert_string_to_file($xmlString, $pdfPath, &$msgs = array())
    {
	$pathAndArgs = $this->getCommandLine();
	$pathAndArgs .= ' - -o "' . $pdfPath . '"';
	    
        return $this->convert_internal_string_to_file($pathAndArgs, $xmlString, $msgs);
    }

    // Old name for backwards compatibility
    public function convert1($xmlPath, &$msgs = array())
    {
	return $this->convert_file($xmlPath, $msgs);
    }

    // Old name for backwards compatibility
    public function convert2($xmlPath, $pdfPath, &$msgs = array())
    {
	return $this->convert_file_to_file($xmlPath, $pdfPath, $msgs);
    }

    // Old name for backwards compatibility
    public function convert3($xmlString)
    {
	return $this->convert_string_to_passthru($xmlString);
    }

    private function getCommandLine()
    {
	$cmdline = $this->exePath . ' --server ' . $this->styleSheets;

	if ($this->isHTML)
	{
	    $cmdline .= '--input=html ';
	}

	if ($this->baseURL != '')
	{
	    $cmdline .= "--baseurl='" . $this->baseURL . "' ";
	}

	if ($this->doXInclude == false)
	{
	    $cmdline .= '--no-xinclude ';
	}

	if ($this->httpUser != '')
	{
	    $cmdline .= "--http-user='" . $this->httpUser . "' ";
	}

	if ($this->httpPassword != '')
	{
	    $cmdline .= "--http-password='" . $this->httpPassword . "' ";
	}

	if ($this->logFile != '')
	{
	    $cmdline .= "--log='" . $this->logFile . "' ";
	}

	if ($this->embedFonts == false)
	{
	    $cmdline .= '--no-embed-fonts ';
	}

	if ($this->compress == false)
	{
	    $cmdline .= '--no-compress ';
	}

	if ($this->encrypt)
	{
	    $cmdline .= '--encrypt ' . $this->encryptInfo;
	}

	return $cmdline;
    }

    private function convert_internal_file_to_file($pathAndArgs, &$msgs)
    {
	$descriptorspec = array(
				0 => array("pipe", "r"),
				1 => array("pipe", "w"),
				2 => array("pipe", "w")
				);
	
	$process = proc_open($pathAndArgs, $descriptorspec, $pipes);
	
	if (is_resource($process))
	{
	    $result = $this->readMessages($pipes[2], $msgs);

	    fclose($pipes[0]);
	    fclose($pipes[1]);
	    fclose($pipes[2]);
	    
	    proc_close($process);

	    return ($result == 'success');
	}
	else
	{
	    throw new Exception("Failed to execute $pathAndArgs");
	}
    }

    private function convert_internal_string_to_file($pathAndArgs, $xmlString, &$msgs)
    {
	$descriptorspec = array(
			    0 => array("pipe", "r"),
			    1 => array("pipe", "w"),
			    2 => array("pipe", "w")
			    );
	
	$process = proc_open($pathAndArgs, $descriptorspec, $pipes);
	
	if (is_resource($process))
	{
	    fwrite($pipes[0], $xmlString);
	    fclose($pipes[0]);
	    fclose($pipes[1]);

	    $result = $this->readMessages($pipes[2], $msgs);
	    
	    fclose($pipes[2]);
	
	    proc_close($process);

	    return ($result == 'success');
	}
	else
	{
	    throw new Exception("Failed to execute $pathAndArgs");
	}
    }

    private function convert_internal_string_to_passthru($pathAndArgs, $xmlString)
    {
	$descriptorspec = array(
			    0 => array("pipe", "r"),
			    1 => array("pipe", "w"),
			    2 => array("pipe", "w")
			    );
	
	$process = proc_open($pathAndArgs, $descriptorspec, $pipes);
	
	if (is_resource($process))
	{
	    fwrite($pipes[0], $xmlString);
	    fclose($pipes[0]);
	    fpassthru($pipes[1]);
	    fclose($pipes[1]);

	    $result = $this->readMessages($pipes[2], $msgs);
	    
	    fclose($pipes[2]);
	
	    proc_close($process);

	    return ($result == 'success');
	}
	else
	{
	    throw new Exception("Failed to execute $pathAndArgs");
	}
    }

    private function readMessages($pipe, &$msgs)
    {
	while (!feof($pipe))
	{
	    $line = fgets($pipe);
	    
	    if ($line != false)
	    {
		$msgtag = substr($line, 0, 4);
		$msgbody = rtrim(substr($line, 4));
		
		if ($msgtag == 'fin|')
		{
		    return $msgbody;
		}
		else if ($msgtag = 'msg|')
		{
		    $msg = explode('|', $msgbody, 4);

		    // $msg[0] = 'err' | 'wrn' | 'inf'
		    // $msg[1] = filename / line number
		    // $msg[2] = message text, trailing newline stripped

		    $msgs[] = $msg;
		}
		else
		{
		    // ignore other messages
		}
	    }
	}
	
	return '';
    }
}
?>



forumRoot/images/buttons/pdf.gif
downloaded freely from
http://rip747.wordpress.com/2007/10/18/famfamfam-silk-icons-converted-to-gifs/
use the page_white_acrobat.gif renamed as pdf.gif



now you mke this file anywhere on your PC

product-SaveAsPdf.xml
<?xml version="1.0" encoding="ISO-8859-1"?>

<product productid="PrinceThreadToPdf" active="1">
	<title>Save Thread As PDF with Prince library</title>
	<description>Outputs the print version of a thread in PDF format</description>
	<version>1.0.0</version>
	<url>http://</url>
	<versioncheckurl><![CDATA[http://]]></versioncheckurl>
	<dependencies>
		<dependency dependencytype="php" minversion="4.0.4" maxversion="" />
	</dependencies>
	<codes>
	</codes>
	<templates>
	</templates>
	<plugins>
		<plugin active="1" executionorder="5">
			<title><![CDATA[Add "Save as PDF" to Thread Tools in 'SHOWTHREAD' template]]></title>
			<hookname>parse_templates</hookname>
			<phpcode><![CDATA[if (isset($vbulletin->templatecache['SHOWTHREAD']))
{
	$find = '<tr>
		<td class=\"vbmenu_option\"><img class=\"inlineimg\" src=\"$stylevar[imgdir_button]/printer.gif\" alt=\"$vbphrase[show_printable_version]\" /> <a href=\"printthread.php?" . $GLOBALS[\'vbulletin\']->session->vars[\'sessionurl\'] . "t=$threadid\" accesskey=\"3\" rel=\"nofollow\">$vbphrase[show_printable_version]</a></td>
	</tr>';
	
	$append = '<tr>
		<td class=\"vbmenu_option\"><img class=\"inlineimg\" src=\"$stylevar[imgdir_button]/pdf.gif\" alt=\"$vbphrase[download_as_pdf]\" /> <a href=\"threadtopdf.php?" . $GLOBALS[\'vbulletin\']->session->vars[\'sessionurl\'] . "t=$threadid&amp;pdf=1\">$vbphrase[download_as_pdf]</a></td>
	</tr>';

	$vbulletin->templatecache['SHOWTHREAD'] = str_replace($find, $find . "\n" . $append, $vbulletin->templatecache['SHOWTHREAD']);

	unset($find, $append);
}]]></phpcode>
		</plugin>
	</plugins>
	<phrases>
		<phrasetype name="Show Thread" fieldname="showthread">
			<phrase name="save_as_pdf" date="1183822201" username="Pat Pat" version="1.0.0"><![CDATA[Save as PDF]]></phrase>
		</phrasetype>
	</phrases>
	<options>
	</options>
	<helptopics>
	</helptopics>
	<cronentries>
	</cronentries>
	<faqentries>
	</faqentries>
</product>



After copying the files, go to the Admin CP just install the product choosing the xml file; you "should" be done. There's going to be available an option on the tools pull down menu ofering to save the thread as a pdf file.

Notes:
1) the printing strategy was to take the vBulleting see thread printable version and sending that to Prince, therefore the saved file will have pictures but no signatures nor avatars either.
2) the file threadtopdf.php has some left-overs from testing code and previous save to pdf versions...
3) the final part of the threadtopdf.php file has to be improved in order to avoid the potential php dead-lock metioned on these thread..
http://princexml.com/bb/viewtopic.php?t=1210
http://www.princexml.com/bb/viewtopic.php?t=1006
sure mikeday will help us on this...

This mod was/is running and tested on a Intel Solaris 10 with the pre 6.0r7 version for this platform.


here you have a demo pdf file saved with this mod that looks pretty neat
http://www.mediafire.com/download.php?h5mztoubaz1

see you
Patrick