mirror of
https://github.com/itflow-org/itflow
synced 2026-09-08 07:45:11 +00:00
Enhancement: Files: Document and Files are easily distinguashable now with the option to choose all docs or just files. Thumbnails view now shows all files, Previews now available for documents, pdfs, txts etc, List view and Thumbnail view now retain in search param when navigating folders
This commit is contained in:
@@ -286,3 +286,173 @@ function filterEmailableAttachments($attachments) {
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/*
|
||||
* FontAwesome icon name for a file extension.
|
||||
*
|
||||
* Lived in client/functions.php until agent/files.php needed the same mapping
|
||||
* for its gallery view. The portal reaches this file through the root
|
||||
* functions.php it already loads, so moving it up costs the portal nothing and
|
||||
* leaves one list of extensions rather than two to drift apart.
|
||||
*/
|
||||
function getFileIcon($file_extension) {
|
||||
$file_extension = strtolower($file_extension);
|
||||
|
||||
// Document icons
|
||||
if (in_array($file_extension, ['pdf'])) {
|
||||
return 'file-pdf';
|
||||
} elseif (in_array($file_extension, ['doc', 'docx'])) {
|
||||
return 'file-word';
|
||||
} elseif (in_array($file_extension, ['xls', 'xlsx'])) {
|
||||
return 'file-excel';
|
||||
} elseif (in_array($file_extension, ['ppt', 'pptx'])) {
|
||||
return 'file-powerpoint';
|
||||
} elseif (in_array($file_extension, ['txt', 'md', 'rtf'])) {
|
||||
return 'file-alt';
|
||||
} elseif (in_array($file_extension, ['zip', 'rar', '7z', 'tar', 'gz'])) {
|
||||
return 'file-archive';
|
||||
} elseif (in_array($file_extension, ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'])) {
|
||||
return 'file-image';
|
||||
} elseif (in_array($file_extension, ['mp4', 'avi', 'mov', 'wmv', 'flv'])) {
|
||||
return 'file-video';
|
||||
} elseif (in_array($file_extension, ['mp3', 'wav', 'ogg', 'flac'])) {
|
||||
return 'file-audio';
|
||||
} elseif (in_array($file_extension, ['html', 'htm', 'css', 'js', 'php', 'py', 'java'])) {
|
||||
return 'file-code';
|
||||
} else {
|
||||
return 'file';
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* MIME types safe to render inline in a browser.
|
||||
*
|
||||
* Anything not on this list is served Content-Disposition: attachment - HTML
|
||||
* and SVG in particular are stored-XSS vectors when a browser renders them from
|
||||
* our own origin. Four file-serving entry points enforced it from four
|
||||
* identical copies of the array; the gallery preview in agent/files.php has to
|
||||
* agree with them about what it can frame, so the list is defined once here.
|
||||
*
|
||||
* application/json is on the list because a browser renders it as text, not as
|
||||
* markup, and every serving point sends X-Content-Type-Options: nosniff so it
|
||||
* cannot be re-interpreted as HTML.
|
||||
*/
|
||||
function getInlineViewableMimeTypes() {
|
||||
return [
|
||||
"application/pdf",
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/gif",
|
||||
"image/webp",
|
||||
"text/plain",
|
||||
"application/json"
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
* Tidies raw text into something worth showing in a preview tile.
|
||||
*
|
||||
* Two callers, two different messes, one clean-up:
|
||||
*
|
||||
* documents - document_content_raw is TinyMCE HTML that has been through
|
||||
* strip_tags(), which removes the TAGS and leaves the ENTITIES.
|
||||
* A paragraph of "Hello world & friends" arrives with
|
||||
* those sequences intact, and escaping it for output turned them
|
||||
* into a literal   on screen. Decode first, escape last.
|
||||
*
|
||||
* text files - whatever bytes are on disk: a UTF-8 BOM that renders as a
|
||||
* stray glyph, CRLF line endings, stray control characters, and
|
||||
* the possibility that a file claiming text/plain is not text.
|
||||
*
|
||||
* Returns '' when there is nothing worth showing, so callers fall back to the
|
||||
* file-type icon rather than printing noise.
|
||||
*/
|
||||
function cleanTextExcerpt($text, $length = 400) {
|
||||
$text = (string) $text;
|
||||
|
||||
// A BOM is invisible metadata to a text editor and a stray glyph in HTML
|
||||
$text = preg_replace('/^\xEF\xBB\xBF/', '', $text);
|
||||
|
||||
// Entities left behind by strip_tags(), plus TinyMCE's non-breaking spaces,
|
||||
// which are U+00A0 once decoded and read as odd gaps
|
||||
$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||
$text = str_replace("\xC2\xA0", ' ', $text);
|
||||
|
||||
// Drop anything that is not valid UTF-8 rather than letting a half-decoded
|
||||
// byte render as a replacement character
|
||||
if (!mb_check_encoding($text, 'UTF-8')) {
|
||||
$text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');
|
||||
}
|
||||
|
||||
// Control characters, keeping the two that carry meaning in a preview.
|
||||
// No /u modifier on purpose: these are all single-byte ASCII, and every
|
||||
// continuation byte of a multi-byte character is >= 0x80, so a byte-wise
|
||||
// strip cannot damage one. With /u the whole call returns null the moment
|
||||
// the subject holds a stray invalid byte, blanking the excerpt instead of
|
||||
// cleaning it.
|
||||
$text = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $text);
|
||||
|
||||
$text = str_replace(["\r\n", "\r"], "\n", $text);
|
||||
|
||||
// Stripped HTML leaves long runs of blank lines and indentation
|
||||
$text = preg_replace('/[ \t]+/', ' ', $text);
|
||||
$text = preg_replace('/\n{2,}/', "\n", $text);
|
||||
$text = trim($text);
|
||||
|
||||
// Cut on a character boundary - a byte-wise substr can split a multi-byte
|
||||
// character and leave a broken glyph at the end of every tile
|
||||
if (mb_strlen($text, 'UTF-8') > $length) {
|
||||
$text = mb_substr($text, 0, $length, 'UTF-8') . '...';
|
||||
}
|
||||
|
||||
return (string) $text;
|
||||
}
|
||||
|
||||
/*
|
||||
* First few hundred characters of a text file, for a grid tile preview.
|
||||
*
|
||||
* Reads a bounded chunk rather than the whole file - a 40MB log has no business
|
||||
* being loaded into a page that shows two dozen tiles - and re-applies the same
|
||||
* realpath containment check the file-serving endpoints use, because the caller
|
||||
* is building a path from a database column.
|
||||
*
|
||||
* Returns '' when the file is missing, unreadable, escapes uploads/, or does
|
||||
* not look like text at all. The last case matters: a mislabelled binary served
|
||||
* as text/plain would otherwise fill the tile with garbage.
|
||||
*/
|
||||
function getFileTextExcerpt($client_id, $file_reference_name, $length = 400) {
|
||||
$client_id = intval($client_id);
|
||||
|
||||
$uploads_base = realpath(__DIR__ . "/../uploads");
|
||||
$file_path = realpath(__DIR__ . "/../uploads/clients/$client_id/$file_reference_name");
|
||||
|
||||
if ($file_path === false || $uploads_base === false || strpos($file_path, $uploads_base) !== 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!is_file($file_path) || !is_readable($file_path)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$handle = fopen($file_path, 'rb');
|
||||
if ($handle === false) {
|
||||
return '';
|
||||
}
|
||||
// Read well past the target so cleaning and the character-boundary cut
|
||||
// still have a full excerpt to work with
|
||||
$raw = fread($handle, $length * 4);
|
||||
fclose($handle);
|
||||
|
||||
if ($raw === false || $raw === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Does this actually look like text? Count the bytes no text file should
|
||||
// carry; a few percent is a mislabelled binary, not a stray character.
|
||||
$control_bytes = strlen(preg_replace('/[^\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $raw));
|
||||
if ($control_bytes > 0 && ($control_bytes / strlen($raw)) > 0.05) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return cleanTextExcerpt($raw, $length);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user