Using FPDI and FDP to generate slightly different pdf files - php

i first import a pdf using fpdi to make a fpdf object, i then perform several changes on that pdf. I clone it to make a custom pdf just adding some texts. Then i output the two files to disk but just one is created and i got a fatal error for the second output :
Fatal error: Call to undefined method stdClass::closeFile() in C:\Program Files\EasyPHP 3.0\www\oursin\oursin\public\scripts\FPDI\fpdi.php on line 534
pieces of my code:
$pdf = new FPDI('L','mm',array(291.6,456));
$fichier=$repertoireGrilles.'GR_IFR.pdf';
$pdf->setSourceFile($fichier);
// add a page
$tplIdx = $pdf->importPage(1);
$pdf->AddPage();
$pdf->useTemplate($tplIdx,0,0,0);
..
...
methods on $pdf
..
..
..
$pdfCopie=clone $pdf;
methods on $pdfCopie
$pdfCopie-> Output($repertoireGrilles.'grillesQuotidiennes/'.$date.'/Grille_'.$date.'_'.$ou.'_copie.pdf','F');
$pdf-> Output($repertoireGrilles.'grillesQuotidiennes/'.$date.'/Grille_'.$date.'_'.$ou.'.pdf','F');
Anybody to help me to tackle this issue that keeps my brain under high pressure for hours (days) :) ?

Cloning, forking, copying, any of that is really dirty. You will have a very hard time with outputs if you take that route. Instead, consider this approach:
Make multiple AJAX calls to a single PHP file, pass a pid value to it so as to differentiate between them.
Go through the exact same document setup for FPDI. This is far more consistent than cloning, forking, copying, etc.
Check pid and do different things to different documents after all the setup is done.
Output the documents.
Here is my jQuery:
$(document).ready(function(){
var i;
for( i=0; i<=1; i++ )
{
$.ajax({
url: 'pdfpid.php',
data: {
pid: i,
pdf: 'document.pdf'
},
type: 'post'
});
}
});
As you can see, it's pretty simple. pdfpid.php is the name of the file that will generate and process the documents. In this case, I want the document with a pid of 0 to be my "original" and the one with a pid of 1 to be the "cloned" document.
// Ensure that POST came in correctly
if( !array_key_exists('pid',$_POST) || !array_key_exists('pdf',$_POST) )
exit();
// Populate necessary variables from $_POST
$pid = intval($_POST['pid']);
$src = $_POST['pdf'];
// Setup the PDF document
$pdf = new FPDI();
$pdf->setSourceFile($src);
$templateID = $pdf->importPage(1);
$pdf->addPage();
$pdf->useTemplate($templateID);
$pdf->SetFont('Arial','B',24);
switch( $pid )
{
default:
break;
case 0:
// "Parent" document
$pdf->Text(10,10,"ORIGINAL");
$filename = "original.pdf";
break;
case 1:
// "Child" document
$pdf->Text(10,10,"CLONED");
$filename = "cloned.pdf";
break;
}
$pdf->Output($filename,'F');
I got both documents as an output, with the unique modifications between the "parent" and the "child" all in place.

Related

Read and move a file so that only one user can access that file

I have a folder called 'BinFolder' which has 6 files
file1
file2
file3
file4
file5
file6
When 2 users need random files at the same time eg
User 1 wants a file(say file1)
User 2 wants a file(say file5 , but not file1)
How can I get this with fileread..php avoiding file repetition?
<script>
var button = document.getElementById('test');
button.addEventListener('click', () => {
<?php
$path = 'hdcp';
$files = scandir($path);
$files = array_diff(scandir($path), array('.', '..'));
$files = array_diff(scandir($path), array('.', '..'));
$rand = $files[array_rand($files)];
?>
document.getElementById('log2').innerHTML += "<?php echo $rand ;?><br/>" ;
fetch('hdcp/<?php echo $rand ;?>')
.then(response => response.arrayBuffer())
.then(data => {
var byteArray = new Uint8Array(data);
console.log(byteArray);
})
</script>
OK, So you can achieve your desired behavior by implementing promise in PHP, as probably you know, PHP is a single tread application that does not have a promise by default (have 1 memory heap and 1 call stack). but if you want to do such behavior, there are some libraries that you can use :
if you are using PHP >= 8.1 :
you can use fiber
if you are using PHP <8.1 :
you can use reactphp
those are two high star/recommended libraries for implementing "promise" in PHP.
as you say yourself in the comment :
For two simultaneous users, one user should wait until the other user completes a request
and it's exactly what you should do in case of your requirement.
react PHP is a low-level library that has an event loop in its core so that it makes it possible to run codes async.
for more explanation, I can say that a promise is an object that establishes a relationship between Producing Code and Consuming Code :
Producing Code: some codes that require some time to run.
Consuming Code: some codes that wait for the result of producing code.
first of all, for installing ReactPHP you must add it to your project by the composer :
composer require react/promise
now you must to create an promise object:
use React/Promise/Promise;
$promise = new Promise($resolver, $canceller)
you can set $resolver and $canceller as bellow :
resolver
$resolver = function (callable $resolve, callable $reject) {
// Do some work, possibly asynchronously, and then
// resolve or reject.
$resolve('Promise is resolved');
// or throw new Exception('Promise rejected');
// or $resolve($anotherPromise);
// or $reject($nastyError);
};
canceller
$canceller = function () {
// Cancel/abort any running operations like network connections, streams etc.
// Reject promise by throwing an exception
throw new Exception('Promise cancelled');
};
and in the end for using them you can write such a scripts like :
$promise->then(
fn($value) => echo '$value' ;
fn($value) => echo '$value' ;
the first closure will be run in the case of running promise successfully and the second one will be run if the promise rejects.

Why am I getting an "Empty Response" when changing source files in setasign?

I'm using setasign V1 as our servers are yet to be updated past 5.5.9 and am trying to import the first page from 3 different pdf's to create a new pdf. But if I use any more than one file, I get an error.
I am also running this using the Slim framework and have had to add namespaces to each of the setasign files to get them to read in.
It works perfectly with single files, but not when trying to get a page out of multiple files.
In my below code, the loop goes through each file to get the first page and then add some text. This is a function where I pass in an array of "jobs" and also the $savedFile path to save to.
If I put the line "$pdf->setSourceFile($file)" before the loop and define $file as any of the files in the $inputFiles array, it works fine but obviously has just the first page of the first file on each of the 3 pages created. It also works if I make it bring in just the second file or just the third. But if I put this line inside the loop I get an Empty Response error.
I have also tried to unset the parser at the end of each loop but that didn't make any difference.
At the moment, I can't figure out where it's going wrong as no error details are thrown.
$pdf = new FPDI();
$inputFolder = $_SERVER["DOCUMENT_ROOT"] . '/' . $app->config->get('saveLocations.COC') . '/';
$inputFiles = [];
foreach($jobs as $job){
$coc = $app->coc_approval->getCOC($app, $job);
$cocFile = $coc->file;
array_push($inputFiles, $inputFolder . $cocFile);
}
foreach($inputFiles as $file){
$pdf->setSourceFile($file);
$pdf->AddPage();
$tpl = $pdf->importPage(1);
$pdf->useTemplate($tpl, 0, 0, null, null);
// Set font and color
$pdf->SetFont('Helvetica');
$pdf->SetFontSize('10');
$pdf->SetTextColor(0, 0, 0); // RGB
// Add Customer and PO details
$x = 75.5;
$pdf->SetXY($x,67);
$pdf->Cell(0, 15, $customer);
$pdf->SetXY($x, 73);
$pdf->Cell(0, 15, $PO);
//unset($pdf->parsers[$file]);
}
$pdf->Output($savedFile, 'F');
I am expecting the first page of each file listed in the $inputFiles array to be saved in the final output pdf.
Any advice would be very much appreciated thanks!

How to store data in an image with php?

I have tried to use the imagick library to create two functions like this:
function storeCoordinatesImage($img_path, $coordinates){
$im = new imagick($img_path);
$im->setImageProperty("coords", $coordinates);
$im->writeImage($img_path);
}
function getCoordinatesImage($img_path){
$im = new imagick($img_path);
return $im->getImageProperty("coords");
}
If I run:
if(!storeCoordinatesImage("I.jpg", "hi")) echo "fal";
echo getCoordinatesImage("I.jpg");
Nothing is returned.
But if I run:
$im = new imagick($img_path);
$im->setImageProperty("coords", "hello");
echo $im->getImageProperty("coords");
it returns "hello".
So it must be some issue with writing to the image? Although none of these functions are returning false. (i.e they are all working)
Use an image's profile payload to store arbitrary data. Although a JSON payload stored in an image comment (i.e. JPG_COM) tag seems the quick-n-easy, several competing technologies exist for this propose. The most popular being exif, but I would recommend xmp.
eXtensible Metadata Platform
In my opinion, xmp seems over engineered, but does offer a platform to ensure all vendor proprietary information is separated via XML namespace.
Wikipedia has a great overview, and Adobe's white papers (pdf) do a great job outlining the "does-n-don'ts" for a vendor to implement.
ImageMagick doesn't handle anything outside of read/write profile payloads, so you would be responsible for implementing a XML manager.
For example...
// A minimal XMP tempalte
$XMP_BASE='<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMPTk 2.8">'
.'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"></rdf:RDF>'
.'</x:xmpmeta>';
$xmp = new DOMDocument();
$xmp->loadXML($XMP_BASE);
// Create a <rdf:Descriptiom> & <Coords> DOM element.
foreach($xmp->getElementsByTagName('RDF') as $node) {
$coords = $xmp->createElement('Coords', 'hello');
$description = $xmp->createElement('rdf:Description');
$description->setAttribute('about', '');
$description->appendChild($coords);
$node->appendChild($description);
}
$img = new Imagick('rose:');
// Write profile to image.
$img->setImageProfile('xmp', $xmp->saveXML());
$img->writeImage('output.jpg');
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$img2 = new Imagick('output.jpg');
$xmp2 = new DOMDocument();
// Read profile from image.
$xmp2->loadXML($img2->getImageProfile('xmp'));
// Grab `Coords' value
foreach($xmp2->getElementsByTagName('Coords') as $coords) {
print $coords->textContent . PHP_EOL;
}
//=> "hello"
And you can verify with the identify utility.
identify -verbose output.jpg | grep Coords
#=> Coords: hello
Of course, you would also need to implement a XML DOM "merge" method in the event that an image already contains a XMP profile, and you don't wish to overwrite existing data.
As Ben mentioned this is not possible. Instead you can add a "comment":
function storeCommentImage($img_path, $coordinates){
$im = new imagick($img_path);
$im->commentImage($coordinates);
return $im->writeImage($img_path);
}
function getCommentImage($img_path){
$im = new imagick($img_path);
return $im->getImageProperty("comment");
}
Seems like you can't persist that data for jpegs: https://github.com/ImageMagick/ImageMagick/issues/55#issuecomment-157114261
Maybe try with a png?

Image from database into PDF using FPDF

I have an image that is sent from an iPad app to an SQL database. I can retrieve this image and display in a web page using the following php:
$img = base64_encode($row['photoData']);
echo "<img src=\"data:image/jpg;charset=utf8;base64, $img\"/>";
This displays fine. What I want to do now is put this image into a PDF document using FPDF however I am struggling to do this.
This:
$img = base64_encode($row['photoData']);
$pdf->Image($img);
give this error:
FPDF error: Image file has no extension and no type was specified:
So I tried this (although I realise I will then have to look at how to get the size of the image sorted):
$pdf->Image($img, 20, 20, 20, 20 'JPG');
which give me:
FPDF error: Missing or incorrect image file:
What is the correct way to do this?
Or would it be easier to temporarily save the image to the server and then place the saved image into the PDFdoc?
As mentioned in the comments above this is possible by using a stream ("data url") to hand over the image data to the fpdf library without writing physical files to disk:
<?php
// load the 'fpdf' extension
require('fpdf.php');
// just for demonstration purpose, the OP gets the content from a database instead
$h_img = fopen('img.jpg', "rb");
$img = fread($h_img, filesize('img.jpg'));
fclose($h_img);
// prepare a base64 encoded "data url"
$pic = 'data://text/plain;base64,' . base64_encode($img);
// extract dimensions from image
$info = getimagesize($pic);
// create a simple pdf document to prove this is very well possible:
$pdf = new FPDF();
$pdf->AddPage();
$pdf->SetFont('Arial','B',16);
$pdf->Cell(40,10,'Hello Image!');
$pdf->Image($pic, 10, 30, $info[0], $info[1], 'jpg');
$pdf->Output();
If this is a good advice is another question, this is merely meant to prove that this is possible...
According to the Docs FPDF::Image accepts a filename as the first argument, not a binary blob.
If you want to use FPDF specifically, save the image to a temporary file first, and then pass that to FPDF::Image.
To do that, something like this should work:
$tmpFile = tempnam(sys_get_temp_dir(), 'fpdfimg');
if (file_put_contents($tmpFile, $row['photoData'])) {
$fpdf->Image($tmpFile);
// save/display image
unlink($tmpFile);
}
Alternatively, if you want to just serve the image as a PDF (with no other content) you could use Imagick:
$im = new \Imagick();
$im->readImageBlob($row['photoData']);
$im->setImageFormat('pdf');
header('Content-Type: application/pdf');
echo $im;
Since FPDF cannot use base64 data to produce images on the PDF, I would recommend saving the file to the disk permanently as opposed to writing a temp file for every PDF operation.
This will save you a lot of I/O overhead.
Assuming your table has unique photo_id or photo_name to accompany photoData then you can use something like this to create your images and use them in FPDF.
I will also assume you have a last_update and photo_extension column.
<?php
$path = '/path/to/fpdf/images/';
$filename = $row['photo_id'].'.'.$row['photo_extension'];
$filepath = $path.$filename;
// If a physical file is not available then create it
// If the DB data is fresher than the file then make a new file
if(!is_file($filepath) || strtotime($row['last_update']) > filemtime($filepath))
{
$result = file_put_contents($filepath, $row['photoData']);
if($result === FALSE)
{
die(__FILE__.'<br>Error - Line #'.__LINE__.': Could not create '.$filepath);
}
}
$pdf->Image($filepath);
If you plan on updating the photoData which is stored in your DB then you will have to make sure to also have a timestamp column and compare that timestamp against the filemtime($filepath) of the image on your disk.
Another solution for this ;)
Make a new php by copying and pasting this (piece of fpdf's code edited):
require('fpdf.php');
class DATAIMAGE extends FPDF
{
protected function _parsedata($file)
{
// Extract info from a JPEG file
$a = getimagesizefromstring($file);
if(!$a)
$this->Error('Missing or incorrect image file: '.$file);
if($a[2]!=2)
$this->Error('Not a JPEG file: '.$file);
if(!isset($a['channels']) || $a['channels']==3)
$colspace = 'DeviceRGB';
elseif($a['channels']==4)
$colspace = 'DeviceCMYK';
else
$colspace = 'DeviceGray';
$bpc = isset($a['bits']) ? $a['bits'] : 8;
return array('w'=>$a[0], 'h'=>$a[1], 'cs'=>$colspace, 'bpc'=>$bpc, 'f'=>'DCTDecode', 'data'=>$file);
}
}
Then call this php instead of fpdf.php in your main php.
You'll now be able to display an image simply by adding 'data' to the end of the function:
$pdf->Image($mysqlrow["blob"],0,0,40,0,'data');

Is it possible to combine ImageNow with Javascript and php?

I currently have an internal site for my company where our customer support users will be uploading files from our clients. Originally, I had planned on using the help of mysql and a protected, shared network folder and used mysql to hold the filename and path. However, we also utilize ImageNow for other processes. Does anyone know if ImageNow works with javascript and php outside of the software itself? I'm new to ImageNow so any advice is appreciated.
logObArray = getDocLogobArray(workingDoc);
for (var i=0; i<logObArray.length; i++)
{
var docObj = logObArray[i];
var filePath = docObj.filePath;
var fileType = docObj.fileType;
var ftToCheck = fileType.toUpperCase();
var phsobID = docObj.phsobId;
//write OSM info to the file, you'll have to add the other code around this but premise is correct and tested
var outRow = filePath;
outRow = outRow + '\n';
if (Clib.fputs(outRow, outCsvFP) >= 0)
{
debug.log('DEBUG', 'Wrote OSM Path [%s] to file successfully.\n', filePath);
stats.inc('Wrote OSM Path to file');
}
}
Unfortunately, ImageNow doesn't let you get at the information it stores outside of Perceptive Software provided tools. Even if you dig directly into the SQL database and look at the filesystem where it is storing the files, you can't get the information out. ImageNow stores the files unencrypted on the filesystem, so that's fine, and it stores the metadata for those images in easy to search tables in the database. However, the path from the metadata to the filesystem it encrypts before it stores it in the database. So if you are trying to go from the metadata to the images, the farthest along you can get is to the encrypted path. Without the decryption key, you can't get to the images.
However, there is a way you can write code to use ImageNow data. You need the add-on Message Agent - which you need to purchase from Perceptive. That opens up interfaces for using web services and SOAP to get at the ImageNow data.
This is the complete solution for this. It gets the root file and subsequent pages. All other solutions I've found do not get anything other than the first page of the scanned document. Change your drawer to your own drawer name (btw). I hope this helps someone. Companies that lock down people's content really make me mad. Just use the intool.exe utility. It's located in the /bin folder of your installation. The call is: intool --cmd run-iscript --file yourfile.js
var curDocId = 0;
var more = true;
// printf("curDocId : %s\n", curDocId );
while (more) {
var rulestext = "[drawer] = 'AR' AND [docID] > '" + curDocId + "'";
var items = INDocManager.getDocumentsByVslQuery(rulestext, 1000, more, "DOCUMENT_ID");
var start = items[0];
var dataDesc = new Array();
var headerDelim = "\03"
var dataDelim = "\02";
for (var line=1; line <= start; line++) {
var temp = items[line].split(headerDelim);
dataDesc[temp[1].toUpperCase()] = new Object();
dataDesc[temp[1].toUpperCase()].idx = line - 1;
dataDesc[temp[1].toUpperCase()].name = temp[1];
dataDesc[temp[1].toUpperCase()].datatype = temp[2];
}
for ( ; line < items.length; line++) {
var doc = new INDocument(items[line].split(dataDelim)[dataDesc["DOCUMENT ID"].idx]);
doc.id = items[line].split(dataDelim)[dataDesc["DOCUMENT ID"].idx];
doc.getInfo();
var masterDocId = doc.id;
var itCounter = 150;
var i = 1;
for( ; i <= itCounter; i++)
{
doc.getInfo();
var logob = INLogicalObject( doc.id, -1, i );
logob.retrieveObject();
if(logob && logob.logobCount > 0)
{
var fp = Clib.fopen("c:\\inowoutput.txt", "a");
var line = masterDocId + ',' + logob.id + ',' + logob.workingName + ',' + logob.filePath + '\n';
Clib.fputs(line, fp);
Clib.fclose(fp);
}
else
{
break;
}
}
curDocId = doc.id;
}
//printf("curDocId : %s\n", curDocId );
}
ImageNow has a scripting language that lets you get past the encrypted file path in the database. The file path is available in an undocumented member of the INLogicalObject. Details below for accessing taken from the following blog article. Accessing the encrypted file path in ImageNow
A search of the ImageNow 6.x specific object documentation will find that the INLogicalObject provides information about the actual files stored in the file system. However, it does not contain any information about the file path. A little closer inspection under the hood of the object reveals that it does have a file path field and the value is not encrypted. It is a member of INLogicalObject. The following very simple example shows finding a single document and displaying its file type and unencrypted file path on the console.
// get a single document
var results = INDocManager.getDocumentsBySqlQuery( "", 1, var more );
if ( results )
{
var doc = results[0];
doc.getInfo();
// get a single page for the document
var logob = INLogicalObject( doc.id, -1, 1 );
logob.retrieveObject();
printf( "file type: %s\n", logob.filetype ); // this member is in the documentation
printf( "unencrypted file path: %s\n", logob.filepath ); // this member is not in the documentation
}
Check out External Messaging Agent (EMA) functionality in ImageNow. It's a free module that is available in every installation.
EMA allows you to receive data from outside the ImageNow system (e.g. from a PHP web form, for example).
To use EMA, you merely would need to have the PHP script insert into the IN_EXTERN_MSG and IN_EXTERN_MSG_PROP tables. One of the properties could be the location of the file that was uploaded via PHP.
You'd then need an iScript to parse the data from the EMA tables and create a document in ImageNow.
I've built a solution like this before, and it works pretty well.

Categories