Embedding charts in the PDF using TCPDF library - php

I am working on a project in which a PDF file is to be generated.
I have used Google Chart API for generating different charts. I am using the TCPDF library for converting them into PDF but I am unable to embed these genearted graphs into the PDFs. I think TCPDF does not accept the contents written in the script tag. How can I overcome this problem?

I ran into the same problem except I was using FPDF.
At the end of the day, a PDF file contains static content, so Javascript is out of the question unfortunately. What I ended up doing:
I prepare the chart HTML + Javascript like always and write it to a HTML file in my temp directory. Then I use PhantomJS (http://phantomjs.org/) to create a screenshot of the page which I then include in my PDF (or anywhere, really).
The great thing: it works with ANY local HTML page. If you only have a URL, use file_get_contents() or cURL to retrieve its contents and write it to a local HTML file first.
The guts:
To start, download and extract phantomjs.exe to a directory your application can access. My example uses version 1.9.8 that I copied to lib/phantomjs in my application root.
I have a function that accepts a HTML file path as parameter and a set of PhantomJS options. I suggest adding it to a helper class.
function getHTMLImage($page, $options = array(), $js = null) {
// Prepare the PhantomJS directory that contains phantomjs.exe, e.g. lib or vendor
$phantomDir = $_SERVER['DOCUMENT_ROOT'] . '/lib/phantomjs/';
// Add the PhantomJS directory to the PATH
$origPath = str_replace('"', '', getenv('PATH'));
if (!in_array($phantomDir, explode('/', $origPath)))
putenv('PATH=' . $origPath . '/' . $phantomDir);
// PhantomJS requires a Javascript file to process the request. In case no Javascript processing file is given, use the default
if (is_null($js)) $js = $phantomDir . 'phantom.js';
// Prepare the PhantomJS call
$exec = 'phantomjs --ignore-ssl-errors=yes ' . $js . ' ' . escapeshellarg($page);
// Prepare the PhantomJS options, e.g. width and height
foreach ($options as $option) {
$exec .= ' ' . escapeshellarg($option);
}
// Call PhantomJS. To catch errors, call exec($exec . ' 2>&1', $output, $errorMsg);
exec($exec, $output);
// Decode and return the image data
return ($output ? base64_decode(reset($output)) : false);
}
The Javascript file (mine is called phantom.js and is placed in the same directory as phantomjs.exe):
args = require('system').args;
page = require('webpage').create();
// In this case, I expect these indexes to be the width and height of the chart
if (typeof args[2] !== undefined) {
page.viewportSize = {
width: args[2],
height: (typeof args[3] === undefined ? args[2] : args[3])
};
}
page.open(args[1], function() {
var base64 = page.renderBase64('png');
console.log(base64);
phantom.exit();
});
Call it like this:
// Make sure $width and $height are set, or exclude them altogether
$output = getHTMLImage($htmlPath, array($width, $height));
// Example output, you want to save the image and include it in your PDF file
header('Content-Type: image/png');
exit($output);

Related

FileDrop.js & PHP resulting in empty $_FILES

JSFIDDLE
I'm using filedrop.js to create a file repository structure within my app. The above noted JSFIDDLE has all of the Javascript / jQuery / HTML and CSS code for this small module. While everything on the client end seems to be functioning properly (files can be DnD'd, progress bar acts correctly, console shows proper event triggers), the result on the server-side is always an empty $_FILES variable. My PHP (ajax.receiveFile.php) is as follows:
var_dump($_FILES);
ob_start();
$callback = &$_REQUEST['fd-callback'];
$job_id = &$_REQUEST['job_id'];
$subdir = &$_REQUEST['subdir'];
$j = loadJob($job_id);
$save_path = "D:\\JobFiles\\" . $j->gOrderNumber() . "\\" . $subdir . "\\";
if ( ($_FILES['fd-file']['size'] > 0) && is_uploaded_file($_FILES['fd-file']['tmp_name']) ) {
$name = $_FILES['fd-file']['name'];
if (move_uploaded_file($_FILES['fd-file']['tmp_name'], $save_path.$name)) {
$j->addAttachment($subdir,$name);
echo 'true';
} else {
echo 'false';
}
}
ob_end_flush();
FileDrop.js seems to be doing what it is supposed to do, as shown here:
I read here on SO that using the same element name over multiple input types of "file" can cause errors but I'm not sure that is the case here. I have double- and triple-checked the permissions on both the TEMP and TARGET upload folders, I have confirmed that all PHP variables are set as needed via visual inspection and PHPINFO(). The server config is PHP 5.4 on IIS7.
If anyone has any ideas on what else to look for, please contribute. Thanks!
This works for me:
file_put_contents('uploads/person/7.jpeg', fopen('php://input', 'r'));

Mozilla pdf.js, How to I specify the filename for download?

I pass the location of the php file that contains the following code as parameter to the viewer.html file and it is displayed correctly but when clicking the download button in the pdf viewer the document name is always document.pdf. This poses a problem because of how many mobile users will be downloading files only to discover that all of their files have the the name document.pdf and that they (for most mobile browsers) can't change the filename before downloading.
Do I have to pass some arbitrary parameter to the file or redirect to self with the filename appended?
<?php
$content = "a binary representation of my pdf";
header("Content-type: application/pdf");
header('Content-Transfer-Encoding: binary');
header('Content-Disposition: attachment; filename="someFile.pdf"');
echo $content;
?>
I've run into this same issue. From the pdf.js's viewer.js source:
function getPDFFileNameFromURL(url) {
var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
// SCHEME HOST 1.PATH 2.QUERY 3.REF
// Pattern to get last matching NAME.pdf
var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
var splitURI = reURI.exec(url);
var suggestedFilename = reFilename.exec(splitURI[1]) ||
reFilename.exec(splitURI[2]) ||
reFilename.exec(splitURI[3]);
if (suggestedFilename) {
suggestedFilename = suggestedFilename[0];
if (suggestedFilename.indexOf('%') != -1) {
// URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
try {
suggestedFilename =
reFilename.exec(decodeURIComponent(suggestedFilename))[0];
} catch(e) { // Possible (extremely rare) errors:
// URIError "Malformed URI", e.g. for "%AA.pdf"
// TypeError "null has no properties", e.g. for "%2F.pdf"
}
}
}
return suggestedFilename || 'document.pdf';
}
So the majic needs to come from the URL via the reURI regexp.
What you need to do is this:
http://domain.com/path/to/Named.pdf
http://domain.com/path/to/your/api?fileId=123&saveName=Named.pdf
Each of these will result in a save as filename of Named.pdf thanks to the regexp code above.
Based on comments
You can add this to wherever you're using the viewer.js file.
setTimeout(() => {
// Wait for PDFViewerApplication object to exist
PDFViewerApplication.setTitleUsingUrl('custom-file.pdf');
}, 10);
Then when you download the PDF it will have that filename

sending an uploaded PDF file via POST is becoming corrupt

Im trying to send a pdf file uploaded using JQ to a PHP file which uses CLI and pdf2svg to convert it to an svg file, with the eventual aim to return that svg file so that it can be placed into svg editor.
Currently i have this:
In svg-editor.js:
if(file.type.indexOf("pdf") != -1){
//convert to svg
//load svg string
var reader = new FileReader();
reader.onloadend = function(e) {
$.post("pdfuploads/pdfconvert.php", { 'pdf[]': [e.target.result] })
.done(function(data){
alert("Data Loaded: " + data );
svgCanvas.importSvgString(data, true);
svgCanvas.ungroupSelectedElement()
svgCanvas.ungroupSelectedElement()
svgCanvas.groupSelectedElements()
svgCanvas.alignSelectedElements("m", "page")
svgCanvas.alignSelectedElements("c", "page")
});
};
reader.readAsText(file);
}
The above checks the extension of the file being loaded, if its pdf then it posts it to the php file. (all working fine)
In my php file (its being tied in with drupal):
<?php
$dir = getcwd();
define('DRUPAL_ROOT', $_SERVER['DOCUMENT_ROOT']); //added to make sure its defined as we're outside the use of index.php
chdir(DRUPAL_ROOT);
require_once './includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
chdir($dir);
global $base_url;
$time = microtime();
$handle = fopen("pdfupload-".$time.".pdf", "wb");
if (fwrite($handle, file_get_contents($_POST['pdf'][0])) === FALSE) {
$error = "Cannot write to file";
exit;
}
fclose($handle);
//$file = file_put_contents('pdfupload-'.$time.'.pdf', $_POST['pdf'][0]);
$svg = exec("/usr/local/bin/pdf2svg pdfupload-".$time.".pdf output_page-".$time."%d.svg all");
cg_utils_watch('file pdf value','', $error);
cg_utils_watch('file svg value','', $svg);
echo "<pre>";
print_r($svg);
echo "</pre>";
return $svg;
Ignore the logging and the fact that $svg doesnt return the svg file --
The pdf stream is taken from post, and saved. Unfortunalty someware between upload and saving on the server the pdf file becomes corrupted. It saves a pdf file, but when opening that file its empty, the end result being the svg file thats written to the server in the exec() is also empty.
Does anyone have any ideas / know any better way of doing this?
ok, so turns out that mkl in the comments above was right, the issue was with the reader.readAsText(file) line.
This was fireing before the post, so the PDF file was being posted as text.
following this: http://www.develop.com/htmlfivefileapi i changed the JQ to the following:
if(file.type.indexOf("pdf") != -1){
//convert to svg
//load svg string
var reader = new FileReader();
reader.onloadend = function(e) {
$.post("pdfuploads/pdfconvert.php", { 'pdf[]': [e.target.result] })
.done(function(data){
alert("Data Loaded: " + data );
svgCanvas.importSvgString(data, true);
svgCanvas.ungroupSelectedElement()
svgCanvas.ungroupSelectedElement()
svgCanvas.groupSelectedElements()
svgCanvas.alignSelectedElements("m", "page")
svgCanvas.alignSelectedElements("c", "page")
});
};
reader.readAsDataURL(file);
}
and it now works like a charm :)

html2canvas custom proxy in php

This is my first question on here so be nice....
I am trying to use html2canvas to ultimately get a screenshot of a remote site (user submitted url).
The problem is the cross domain security features. I cannot read from the canvas as it's locked by off site assets.
The solution is to use the proxy feature built into the library.
There are some github projects with python and node.js versions, but I'm needing to do it in php.
There are many topics on HOW to implement the feature and how to get it working, but none really explain how to make your own proxy.
My question is two fold, are there any existing solutions in PHP? and if not, I have a few questions on making my own:
1.) What is the output format of the proxy? json object? the rendered image? the base64 encoded data string?
2.) Do these files need to persist on the server or can they just be rendered then disappear (overwritten)?
This is roughly what i'm thinking:
$img_url = urldecode($_GET['url']);
$img_data = base64_encode(file_get_contents($img_url));
//shouldn't need it since it's not cross domain now, but a CORS header could be inserted
header('content-type: application/json; charset=utf-8');
json_encode("{$_GET['callback']}($img_data)");
I've found the answers to my question.
The proxy feature accepts a jsonp element with the url to the proxied image.
And they do need to be saved on the server while
This is raw, I will update it later, but here is a working PHP proxy script for html2canvas
session_start();
//parse the url sent by the proxy function
//TODO: scrub the input
$img_url = urldecode($_GET['url']);
//test file type
//TODO: test for other cases that don't have a '.'
$pos = strrpos($img_url, '.', -1);
$ext = substr($img_url, $pos);
//set a dir for this request
function randomNumber()
{
return substr(sha1(rand()), 0, 15);
}
if (!isset($_COOKIE["img_path"]))
{
do{
$random = randomNumber();
}while (is_dir('images/' . $random));
setcookie("img_path", $random, time()+3600);
} else {
$random = $_COOKIE["img_path"];
}
is_dir('images/' . $random) ? '' : mkdir('images/' . $random, 0755);
//TODO:catch cases where a filename isn't the last element
$basename = basename($img_url);
$file = 'images/' . $random . '/' . $basename;
//save the image
copy($img_url, $file);
//TODO: don't hardcode the url
$test_location = "http://osc.test/html2canvas2/" . $file;
header('Content-Type: application/javascript');
echo "{$_GET['callback']}(" . json_encode($test_location) . ")";
In case anyone else is looking for a simple PHP proxy, here is a link to a nice one by "Cowboy" Ben Alman:
Simple PHP proxy

How to debug a PHP file that is being called by AJAX?

I have a php file for uploading a picture (it uploads the picture, creates its thumbnail and also adds watermark to the picture). The file is being called by AJAX.
It was working on on my localhost on Windows XP with WAMPSERVER 2.0. Now I have installed Windows Vista and suddenly it doesn't work properly (the picture gets uploaded but the thumbnail and watermark parts don't work).
I'm using exactly the same software to test the application on my local machine (WAMPSERVER 2.0) yet it doesn't work.
How to debug this file? Here's how it looks:
<?php
define('BASE_PATH', substr(dirname(dirname(__FILE__)), 0, -22));
// set the include path
set_include_path(BASE_PATH
. '/../../library'
. PATH_SEPARATOR
. BASE_PATH
. '/library'
. PATH_SEPARATOR
. get_include_path());
// autoload classes from the library
function __autoload($class) {
include str_replace('_', '/', $class) . '.php';
}
$configuration = new Zend_Config_Ini(BASE_PATH
. '/application'
. '/configs/application.ini',
'development');
$dbAdapter = Zend_Db::factory($configuration->database);
Zend_Db_Table_Abstract::setDefaultAdapter($dbAdapter);
function _getTable($table)
{
include BASE_PATH
. '/application/modules/default/models/'
. $table
. '.php';
return new $table();
}
$albums = _getTable('Albums');
$media = _getTable('Media');
if (false === empty($_FILES)) {
$tempFile = $_FILES['Filedata']['tmp_name'];
$extension = end(explode('.', $_FILES['Filedata']['name']));
// insert temporary row into the database
$data = array();
$data['type'] = 'photo';
$data['type2'] = 'public';
$data['status'] = 'temporary';
$data['user_id'] = $_REQUEST['user_id'];
$paths = $media->add($data, $extension, $dbAdapter);
// save the photo
move_uploaded_file($tempFile,
BASE_PATH . '/public/' . $paths[0]);
// create a thumbnail
include BASE_PATH . '/library/My/PHPThumbnailer/ThumbLib.inc.php';
$thumb = PhpThumbFactory::create('/' . $paths[0]);
$thumb->adaptiveResize(85, 85);
$thumb->save(BASE_PATH . '/public/' . $paths[1]);
// add watermark to the bottom right corner
$pathToFullImage = BASE_PATH . '/public/' . $paths[0];
$size = getimagesize($pathToFullImage);
switch ($extension) {
case 'gif':
$im = imagecreatefromgif($pathToFullImage);
break;
case 'jpg':
$im = imagecreatefromjpeg($pathToFullImage);
break;
case 'png':
$im = imagecreatefrompng($pathToFullImage);
break;
}
if (false !== $im) {
$white = imagecolorallocate($im, 255, 255, 255);
$font = BASE_PATH . '/public/fonts/arial.ttf';
imagefttext($im,
13, // font size
0, // angle
$size[0] - 132, // x axis (top left is [0, 0])
$size[1] - 13, // y axis
$white,
$font,
'HunnyHive.com');
switch ($extension) {
case 'gif':
imagegif($im, $pathToFullImage);
break;
case 'jpg':
imagejpeg($im, $pathToFullImage, 100);
break;
case 'png':
imagepng($im, $pathToFullImage, 0);
break;
}
imagedestroy($im);
}
echo "1";
}
For generally debugging this, you should break the steps down into functions and test each part in turn.
With regard to Ajax debugging, you might like to try out Zend_Log_Writer_FirePHP. It's an extension for Firebug which reads extra data in headers sent by PHP, which means that the data in your response body doesn't contain debug output, and for things like images, can still be rendered.
Once you've installed FirePHP for Firebug, you can just write the following few lines:
$wFirebug = new Zend_Log_Writer_Firebug();
$firebug = new Zend_Log($wFirebug);
$firebug->info($myArray);
$firebug->info('Got to line 10');
When debugging anything that is related to AJAX I would recommend the following:
Ensure that the file is returning the correct data without any AJAX wrapper around it. Call the file directly with some sample data. Does it return what you require? Does it have syntax errors? This is the first thing you want to check and it will save you a ton of headache.
Ensure your Javascript is parsing the data correctly. Your JS may be expecting JSON but you're returning XML, or your returned data is not formatted the way you think, or you may need to evaluate your returned data in Javascript so it can actually be used. Try some console.log()'s to test what your data looks like.
Try something like Postbin which lets you send POST data. Ensure your POST data is correct and you're sending the right data to your script.
You could just create a simple form with a file input and just use that for easy testing.
That is the simplest way that I see, someone else may have something better in mind.
I find that when working with an action being called via AJAX in ZF, it's always a good idea to make sure that the action works without AJAX first. That is, make your first iteration of developing the feature result in going to a new page to represent the action you're making.
Once you know that your PHP code works, you can then begin to worry about making the AJAX stuff work. In my opinion, at least, being able to output Zend_Debug::dump() on variables when you view another page is a lot easier for initial development.
AJAX by design creates a layer of opacity which can makes it difficult to do this. It gets even harder if you're interfacing with, for instance, a jQuery plugin that requires formatted data that you're just not getting for some reason. So again, PHP first, AJAX second. It takes roughly two seconds in ZF to go from a PHP to an AJAX call, and vice versa.
The easiest solution would be to use FirePHP; install firebug + firephp addon for firefox and include the classes in your project.
(I keep the FirePHP library in /usr/share/php/ so I can include it easily in any project)
Then just do this:
require_once('/path/to/FirePHPCore/FirePHP.class.php');
$fp = FirePHP::getInstance(true);
$fp->log('you can put anything here, vars, objects, arrays, etc');
It will output the response in the FireBug console and is much better than polluting your code with echos and var_dumps when debugging ajax!
Make the AJAX request with a callback function, which checks the data returned (echo'd) from the PHP function. If the data echo'd is some pre-determined success string ("success"?) then all is well, if it's not that, have the callback function output whatever is output by the function in an alert or something.

Categories