Skip to content

Commit

Permalink
faxsms etherfax module Improvements
Browse files Browse the repository at this point in the history
- several PHP warnins and error fixed
- introduct two new classes to support image to pdf including tiff images
  • Loading branch information
sjpadgett committed May 11, 2024
1 parent f9c8a85 commit f2e67aa
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use OpenEMR\Common\Crypto\CryptoGen;
use OpenEMR\Modules\FaxSMS\EtherFax\EtherFaxClient;
use OpenEMR\Modules\FaxSMS\EtherFax\FaxResult;
use OpenEMR\Services\ImageUtilities\HandleImageController;
use Symfony\Component\HttpClient\HttpClient;

class EtherFaxActions extends AppDispatch
Expand Down Expand Up @@ -372,6 +373,7 @@ public function getPending()
;
$faxStore = $this->fetchFaxQueue($dateFrom, $dateTo);
$responseMsgs = [];
$responseMsgs[0] = '';
$responseMsgs[2] = xlt('Not Implemented');
$direction = 'inbound';
foreach ($faxStore as $faxDetails) {
Expand All @@ -383,10 +385,9 @@ public function getPending()
$from = $faxDetails->CallingNumber;
$params = $faxDetails->DocumentParams;
$form = '';
$docType = null;
$id_esc = text($id);
$showFlag = 0;
$recog = $faxDetails->AnalyzeFormResult->AnalyzeResult->DocumentResults;
$recog = $faxDetails->AnalyzeFormResult->AnalyzeResult->DocumentResults ?? [];
foreach ($recog as $r) {
$details = null;
$form = "<tr id='$id_esc' class='d-none collapse-all'><td colspan='12'>\n" .
Expand All @@ -398,15 +399,15 @@ public function getPending()
xlt("Confidence") . " : " . text($r->DocTypeConfidence * 100) .
"\n</th></tr></thead>\n";
$form .= "<tbody>\n";
$parse = $this->parseValidators($r->Fields);
$parse = $this->parseValidators($r->Fields) ?? null ? $this->parseValidators($r->Fields) : [];
$pid_assumed = '';
if (!empty($parse['DOB']) && !empty($parse['fname']) && !empty($parse['lname'])) {
$pid_assumed = sqlQuery(
"Select pid From patient_data Where fname = ? And lname = ? And DOB = ?",
array($parse['fname'], $parse['lname'], date("Y-m-d", strtotime(($parse['DOB']))))
)['pid'];
}
$pid_assumed = $pid_assumed ?: 'No';
$pid_assumed = $pid_assumed ?? 0 ? $pid_assumed : 'No';
foreach ($r->Fields as $field) {
if ($field->Text == 'unselected' || empty($field->Text)) {
continue;
Expand All @@ -418,16 +419,22 @@ public function getPending()
$form .= '<td>' . text($field->Confidence * 100) . "</td>\n";
$form .= "</tr>\n";
$table[$field->Name] = ['name' => $field->Name, 'value' => $field->Text];
$details['dob'] = $field->Name == "Patient Date of Birth" ? $field->Text : $details['dob'];
$details['dob'] = $field->Name == "Patient Date of Birth" ? $field->Text : $details['dob'] ?? '';
if (preg_match('/(Patient First Name)(Patient Last Name)/', $field->Name)) {
$details['name'][$field->Name] = $field->Text;
}
$details['gender'] = $field->Text == "selected" && (stripos($field->Name, 'Male') !== false) ? 'Male' : $details['gender'];
$details['gender'] = $field->Text == "selected" && (stripos($field->Name, 'Female') !== false) ? 'Female' : $details['gender'];
$details['gender'] = $field->Text == "selected" && (stripos($field->Name, 'Male') !== false) ? 'Male' : $details['gender'] ?? '';
$details['gender'] = $field->Text == "selected" && (stripos($field->Name, 'Female') !== false) ? 'Female' : $details['gender'] ?? '';
}
$form .= "</tbody>\n</table>\n</div>\n</td>\n</tr>\n";
}
$parse = json_encode($parse);

if (!empty($parse)) {
$parse = json_encode($parse);
} else {
$parse = '';
}

$patientLink = "<a role='button' href='javascript:void(0)' onclick=\"createPatient(event, " . attr_js($id) . ", " . attr_js($record_id) . ", " . attr_js($parse) . ")\"> <i class='fa fa-chart-simple mr-2' title='" . xla("Chart fax or Create patient and chart fax to documents.") . "'></i></a>";
$messageLink = "<a role='button' href='javascript:void(0)' onclick=\"notifyUser(event, " . attr_js($id) . ", " . attr_js($record_id) . ", " . attr_js(($pid ?? 0)) . ")\"> <i class='fa fa-paper-plane mr-2' title='" . xla("Notify a user and attach this fax to message.") . "'></i></a>";
$downloadLink = "<a role='button' href='javascript:void(0)' onclick=\"getDocument(event, null, " . attr_js($id) . ", 'true')\"> <i class='fa fa-file-download mr-2' title='" . xla("Download and delete fax") . "'></i></a>";
Expand All @@ -447,7 +454,7 @@ public function getPending()
"</td><td>" . text($faxDetails->PagesReceived) .
"</td><td>" . text($docLen) .
"</td><td class='text-left'>" . $detailLink .
"</td><td class='text-center'>" . text($pid_assumed) .
"</td><td class='text-center'>" . text($pid_assumed ?? 0) .
"</td><td class='text-left'>" . $patientLink . $messageLink . $forwardLink . $viewLink . $downloadLink . $deleteLink .
"</td></tr>";
$responseMsgs[0] .= $form;
Expand All @@ -473,7 +480,6 @@ public function getPending()
*/
public function viewFax(): string
{
$formatted_document = null;
$docid = $this->getRequest('docid');
$isDownload = $this->getRequest('download');
$isDownload = $isDownload == 'true' ? 1 : 0;
Expand All @@ -484,7 +490,6 @@ public function viewFax(): string
}

$faxStoreDir = $this->baseDir;

try {
if (is_numeric($docid)) {
$apiResponse = $this->fetchFaxFromQueue(null, $docid);
Expand All @@ -500,28 +505,35 @@ public function viewFax(): string
$this->setFaxDeleted($apiResponse->JobId);
return json_encode('success');
}
$faxImage = $apiResponse->FaxImage;
$raw = base64_decode($faxImage);

$faxImage = $apiResponse->FaxImage; //base_64
$c_header = $apiResponse->DocumentParams->Type;

if ($c_header == 'image/tiff') {
$faxImage = $this->formatFax($faxImage);
$c_header = 'application/pdf';
}
// TODO unused! why is it here?
if ($c_header == 'application/pdf') {
$ext = 'pdf';
$type = 'Fax';
$formatted_document = 'data:application/pdf;base64, ' . $faxImage;
$dataUrl = 'data:application/pdf;base64, ' . $faxImage;
} elseif ($c_header == 'image/tiff' || $c_header == 'image/tif') {
$ext = 'tiff';
$type = 'Fax';
$formatted_document = 'data:image/tiff;base64, ' . $faxImage;
$dataUrl = 'data:image/tiff;base64, ' . $faxImage;
} else {
$ext = 'txt';
$type = 'Text';
$formatted_document = "data:text/plain, " . $faxImage;
$dataUrl = "data:text/plain, " . $faxImage;
}
// Set up to download file.
if ($isDownload) {
if (!file_exists($faxStoreDir) && !mkdir($faxStoreDir, 0777, true) && !is_dir($faxStoreDir)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $faxStoreDir));
}
$file_name = "{$faxStoreDir}/{$type}_{$docid}.{$ext}";
$raw = base64_decode($faxImage);
file_put_contents($file_name, $raw);
$this->setSession('where', $file_name);
$this->setFaxDeleted($apiResponse->JobId);
Expand All @@ -531,8 +543,16 @@ public function viewFax(): string
return json_encode(['base64' => $faxImage, 'mime' => $c_header]);
}

public function formatFax($encodedFax): string
{
$control = new HandleImageController();
$formatted_document = $control->convertImageToPdf($encodedFax, '', 'gd');

return base64_encode($formatted_document);
}

/**
* @param $content
* @param string $content
* @return void
*/
public function disposeDoc($content = ''): void
Expand Down Expand Up @@ -646,9 +666,13 @@ public function insertFaxQueue($faxDetails): int
public function fetchFaxQueue($start, $end): array
{
$rows = [];
$res = sqlStatement("SELECT `id`, `details_json` FROM `oe_faxsms_queue` WHERE `deleted` = '0' AND (`receive_date` > ? AND `receive_date` < ?)", [$start, $end]);
$res = sqlStatement("SELECT `id`, `details_json`, `receive_date` FROM `oe_faxsms_queue` WHERE `deleted` = '0' AND (`receive_date` > ? AND `receive_date` < ?)", [$start, $end]);
while ($row = sqlFetchArray($res)) {
$detail = json_decode($row['details_json']);
if (json_last_error()) {
error_log('Error: ' . $row['id'] . ': ' . json_last_error_msg() . ' ' . $row['details_json']);
continue;
}
$detail->RecordId = $row['id'];
$rows[] = $detail;
}
Expand All @@ -661,7 +685,7 @@ public function fetchFaxQueue($start, $end): array
* @param $id
* @return mixed
*/
public function fetchFaxFromQueue($jobId, $id = null)
public function fetchFaxFromQueue($jobId, $id = null): mixed
{
if (!empty($jobId)) {
$row = sqlQuery("SELECT `id`, `details_json` FROM `oe_faxsms_queue` WHERE `job_id` = ? LIMIT 1", [$jobId]);
Expand Down
69 changes: 69 additions & 0 deletions src/Services/ImageUtilities/HandleImageController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

/**
* PdfCreator class
*
* @package OpenEMR
* @link http://www.open-emr.org
* @author Jerry Padgett <sjpadgett@gmail.com>
* @copyright Copyright (c) 2024 Jerry Padgett <sjpadgett@gmail.com>
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
*/

namespace OpenEMR\Services\ImageUtilities;

use Exception;
use OpenEMR\Services\ImageUtilities\HandleImageService;

class HandleImageController
{
private HandleImageService $imageService;
private $extension;

public function __construct($extension = 'gd')
{
$this->imageService = new HandleImageService();
$this->extension = $extension;
}

public function isImagickAvailable(): bool
{
return extension_loaded('imagick');
}

public function isGdAvailable(): bool
{
return extension_loaded('gd');
}

public function convertImageToPdf($imageData, $pdfPath = '', $useExt = 'imagick'): false|string
{
$content = '';
if (is_file($imageData)) {
$imageContent = file_get_contents($imageData);
} else {
$imageContent = $imageData;
}

$usingImagick = $useExt === 'imagick' && $this->isImagickAvailable();
$usingGd = $useExt === 'gd' && $this->isGdAvailable() && !$usingImagick;

if ($usingImagick || $usingGd) {
try {
if ($usingImagick) {
$content = $this->imageService->convertImageToPdfUseImagick($imageContent, $pdfPath);
} elseif ($usingGd) {
$content = $this->imageService->convertImageToPdfUseGD($imageContent, $pdfPath);
}
} catch (Exception $e) {
error_log('Error converting image to PDF: ' . $e->getMessage());
return false;
}
} else {
error_log('No suitable image processing library available.');
return false;
}

return $content;
}
}
74 changes: 74 additions & 0 deletions src/Services/ImageUtilities/HandleImageService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace OpenEMR\Services\ImageUtilities;

use Exception;
use Imagick;
use ImagickException;
use OpenEMR\Pdf\MpdfGenericPdfCreator;

class HandleImageService
{
public function convertImageToPdfUseGD($imageData, $pdfPath): false|string
{
try {
$imageRaw = base64_decode($imageData); // Decode base64 image data (if needed)
$image = imagecreatefromstring($imageRaw); // Load image using GD
if ($image === false) {
throw new Exception('Failed to create image from data');
}
ob_start();
imagepng($image);
$imagePngData = ob_get_clean();
imagedestroy($image);

$pdf = new MpdfGenericPdfCreator();
$pdf->addImageToPDF($imagePngData); // Add image to PDF

return $pdf->outputPDF($pdfPath, 'S'); // Output PDF as a string
} catch (Exception $e) {
// Handle exceptions
error_log('Error: ' . $e->getMessage());
return false;
} finally {
// Clean up GD resources
if (is_resource($image)) {
imagedestroy($image);
}
}
}

public function convertImageToPdfUseImagick($imageContent, $pdfOutPath = ''): false|string
{
try {
$imagick = new Imagick();
$imageRaw = base64_decode($imageContent); // Decode base64 image data (if needed)
$imagick->readImageBlob($imageRaw); // Load image using Imagick from binary data
$imagick->setFirstIterator(); // Set iterator to first page
$imagick->setImageFormat('pdf');
$pdfContent = $imagick->getImagesBlob();
} catch (ImagickException $e) {
// Handle Imagick-related exceptions
error_log('Imagick error: ' . $e->getMessage());
return false;
} catch (Exception $e) {
// Handle other exceptions
error_log('Error: ' . $e->getMessage());
return false;
} finally {
// Clean up Imagick resources
if (isset($imagick)) {
$imagick->clear();
$imagick->destroy();
}
}

if ($pdfOutPath) {
// Write the PDF content to a file if $pdfOutPath is provided
file_put_contents($pdfOutPath, $pdfContent);
return true; // Return true if PDF is successfully written to file
}
// Return the PDF content as a string if $pdfOutPath is empty
return $pdfContent;
}
}

0 comments on commit f2e67aa

Please sign in to comment.