This topic is locked

Viewing PDF, docx, xlsx, pptx and images within the application browser

8/2/2026 11:14:08
PHPRunner Tips and Tricks
H
hudster author

PHP Runner currently allows PDF documents to be viewed within the browser. However, word, excel, powerpoint and images are all downloaded.
This solution allows you to view those files within the browser without needing to download.

For this example I am using the library from Docmentis - it is free to use for personal and commercial use. If you want to remove their logo then you will need a licence. I am using the script loaded from their cdn but you can install it on your own server via npm.

In this example I've used PHP Runner's document management template but it will work with your own projects.

img alt

img alt

img alt

img alt

Create a custom PHP page

in this example it is called docmentis_test_stream.php

<?php

require_once("include/dbcommon.php");

if (!Security::isLoggedIn()) {
http_response_code(401);
exit("Not authorised");
}

$id = (int)($_GET["id"] ?? 0);

if ($id <= 0) {
http_response_code(400);
exit("Invalid document ID");
}

$sql = DB::PrepareSQL(
"SELECT id, file, ownerid, share_users, share_type
FROM doc_files
WHERE id = :1",
$id
);

$rs = DB::Query($sql);
$row = $rs ? $rs->fetchAssoc() : null;

if (!$row) {
http_response_code(404);
exit("Document not found");
}

/*
* Basic ownership check.
* You may need to extend this later for shared documents.
*/
$currentUser = Security::getUserName();

if (
isset($row["ownerid"]) &&
(string)$row["ownerid"] !== (string)$currentUser &&
!Security::isAdmin()
) {
/*
* Temporarily comment this block out during testing
* if ownerid contains a numeric user ID rather than username.
*/
}

$fileData = json_decode($row["file"] ?? "", true);

if (
!is_array($fileData) ||
empty($fileData) ||
empty($fileData[0]["name"])
) {
http_response_code(500);
exit("Invalid uploaded-file metadata");
}

$file = $fileData[0];

$filePath = $file["name"];
$fileName =
$file["usrName"] ??
basename($filePath);

$mimeType =
$file["type"] ??
"application/octet-stream";

if (!is_file($filePath)) {
http_response_code(404);
exit("Physical file not found");
}

/*
* Ensure the file is within the expected PHPRunner files directory.
*/
$allowedDirectory = realpath(__DIR__ . "/files");
$realFilePath = realpath($filePath);

if (
$allowedDirectory === false ||
$realFilePath === false ||
strpos($realFilePath, $allowedDirectory . DIRECTORY_SEPARATOR) !== 0
) {
http_response_code(403);
exit("Invalid file path");
}

header("Content-Type: " . $mimeType);
header(
'Content-Disposition: inline; filename="' .
rawurlencode($fileName) . '"'
);
header("Content-Length: " . filesize($realFilePath));
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Pragma: no-cache");

readfile($realFilePath);
exit;

NEXT CREATE A CUSTOM BUTTON IN DESIGNER

in server

$record = $button->getCurrentRecord();

if (!$record) {
$result["success"] = false;
$result["message"] = "The current document record could not be retrieved.";
return;
}

$id = $record["id"] ?? "";
$hash = $record["hash"] ?? "";
$fileName = $record["name"] ?? "";

if ($id === "" || $hash === "" || $fileName === "") {
$result["success"] = false;
$result["message"] = "The protected file details are incomplete.";
$result["record"] = $record;
return;
}

$result["success"] = true;
$result["fileName"] = $fileName;

$result["fileUrl"] =
"doc_files_list.php"
. "?file=" . rawurlencode($fileName)
. "&table=doc_files"
. "&field=file"
. "&pageType=list"
. "&key1=" . rawurlencode((string)$id)
. "&hash=" . rawurlencode((string)$hash);

In client after

if (!result || result.success !== true) {
alert(
result && result.message
? result.message
: "Unable to open the document."
);
return;
}

const fileUrl = result.fileUrl;
const fileName = result.fileName || "Document";

(async function () {
let viewer = null;
let client = null;

try {
const response = await fetch(fileUrl, {
method: "GET",
credentials: "same-origin",
cache: "no-store",
redirect: "follow"
});

if (!response.ok) {
throw new Error(
"PHPRunner returned HTTP " +
response.status +
" while retrieving the document."
);
}

const contentType =
response.headers.get("content-type") ||
"application/octet-stream";

const buffer = await response.arrayBuffer();

if (!buffer.byteLength) {
throw new Error("PHPRunner returned an empty document.");
}

const documentFile = new File(
[buffer],
fileName,
{
type: contentType
}
);

console.log("DocMentis document source", {
fileName: documentFile.name,
contentType: documentFile.type,
size: documentFile.size,
signature: Array.from(
new Uint8Array(buffer).slice(0, 16)
)
});

$("#docmentisButtonModal").remove();

const safeTitle = $("<div>")
.text(fileName)
.html();

$("body").append(`
<div
class="modal fade"
id="docmentisButtonModal"
tabindex="-1"
aria-hidden="true"
>
<div
class="modal-dialog modal-xl"
style="
width:96vw;
max-width:96vw;
height:94vh;
margin:2vh auto;
"
>
<div
class="modal-content"
style="height:94vh;"
>
<div class="modal-header">
<h4 class="modal-title">
${safeTitle}
</h4>

<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">
×
</span>
</button>
</div>

<div
class="modal-body"
style="
padding:0;
height:calc(94vh - 65px);
overflow:hidden;
"
>
<div
id="docmentisButtonViewer"
style="
width:100%;
height:100%;
min-height:600px;
"
></div>
</div>
</div>
</div>
</div>
`);

const $modal = $("#docmentisButtonModal");

$modal.modal("show");

$modal.on("shown.bs.modal", async function () {
try {
const module = await import(
"https://cdn.jsdelivr.net/npm/@docmentis/udoc-viewer@0.7.12/+esm"
);

const UDocClient =
module.UDocClient ||
module.default?.UDocClient ||
module.default;

if (
!UDocClient ||
typeof UDocClient.create !== "function"
) {
throw new Error(
"DocMentis UDocClient could not be loaded."
);
}

client = await UDocClient.create({
googleFonts: true
});

viewer = await client.createViewer({
container:
document.getElementById(
"docmentisButtonViewer"
),

theme: "system",
scrollMode: "continuous",
layoutMode: "single-page",
zoomMode: "fit-spread-width",

hideToolbar: false,
disableSearch: false,
disableThumbnails: false,
disableFullscreen: false
});

viewer.on("error", function (event) {
console.error(
"DocMentis viewer error",
event
);
});

console.log(
"Loading document into DocMentis",
documentFile
);

await viewer.load(documentFile);

console.log(
"DocMentis document loaded successfully",
{
pageCount: viewer.pageCount,
metadata: viewer.metadata
}
);

} catch (error) {
console.error(
"DocMentis initialisation/load error",
error
);

$("#docmentisButtonViewer").html(`
<div
class="alert alert-danger"
style="margin:20px;"
>
**Document could not be opened.**
${
$("<div>")
.text(
error && error.message
? error.message
: String(error)
)
.html()
}
</div>
`);
}
});

$modal.on("hidden.bs.modal", function () {
try {
if (viewer) {
viewer.destroy();
viewer = null;
}

if (client) {
client.destroy();
client = null;
}
} catch (cleanupError) {
console.warn(
"DocMentis cleanup error",
cleanupError
);
}

$(this).remove();
});

} catch (error) {
console.error(
"Protected document retrieval error",
error
);

alert(
error && error.message
? error.message
: "The document could not be retrieved."
);
}
})();

For your own project you will need to change the SQL query in the PHP file and how the document link is formed (in this case it's filename and hash - but might not be in your own project).

And that's it! Enjoy!