Save To Files Developer Guide - edmozley/freeitsm GitHub Wiki
5.2.0 ยท #303 (from pappas81) ยท User guide: Files ยง Saving into Files from a ticket ยท Related: Attach from Files, Files versions
An email attachment on a ticket, or a stored document on any record, can be copied into a Files folder with Save to Files. One small dialog (assets/js/save-to-files.js) serves both, and one endpoint (api/files/save_from.php) checks the source as downloading it is checked and the folder as uploading to it is. A name that is already there gets the same choices as an upload.
It is the opposite direction to Attach from Files: that puts a Files file on a record; this puts a record's file into Files.
| File | What |
|---|---|
includes/services/files_save.php |
FilesSave::targets(), saveDefaults(), source(), ticketFolder(), save(); filesStoreLocalFile()
|
api/files/save_from.php |
GET ?targets=1; POST {source, id, folder_id, ticket_folder?, on_conflict?}
|
assets/js/save-to-files.js (v=1), assets/css/save-to-files.css (v=1) |
window.SaveToFiles.open({source, id, name, ticket}) |
assets/js/inbox.js (v=201), tickets/index.php
|
A Save to Files column in the ticket's attachment list (not for inline images), and a Save to Files button beside each stored file on a ticket's notes (source: 'document'); window.TICKET_SAVE_TO_FILES
|
assets/js/documents.js (v=10), api/documents/list.php, includes/documents_panel.php
|
A Save to Files button on each stored document (kind file); can_save_to_files in the list answer |
api/files/settings.php, files/settings/index.php, assets/js/files-settings.js (v=8) |
Sharing tab: Suggested folder and A folder for each ticket |
lang/en/common.php (save_to_files.*, documents.save_to_files), lang/en/tickets.php, lang/en/files.php
|
Labels |
Settings: files_save_default_folder (a folder id, or empty) and files_save_ticket_folder (1 to start with the ticket-folder box ticked).
// FilesSave::source() - the file, checked AS DOWNLOADING IT IS
// An email attachment: through its ticket (company AND department, 5.1.0) ...
$st = $conn->prepare("SELECT a.filename, a.file_path, e.ticket_id FROM email_attachments a JOIN emails e ON e.id = a.email_id WHERE a.id = ?");
if (!$a || !$a['ticket_id'] || !analystCanAccessTicket($conn, $analystId, (int)$a['ticket_id'])) throw $none;
// ... and the same containment as get_attachment.php: the file must be INSIDE tickets/attachments
$base = realpath(<app>/tickets/attachments); $real = realpath(<app>/tickets/attachments/<file_path>);
if ($base === false || $real === false || strncmp($real, $base . DIRECTORY_SEPARATOR, strlen($base) + 1) !== 0) throw $none;
// A document: documentCanView(), and only kind 'file' - a web link or a Files link has no bytes here
if (!documentCanView($conn, $analystId, $allowed, $id)) throw $none;// FilesSave::save() - the folder, checked AS UPLOADING TO IT IS
if (FilesAcl::level($conn, $analystId, $folderId) < FilesAcl::UPLOAD) throw new ServiceError('forbidden', ...);"No such attachment" and "not yours" are the same not_found, so an id cannot be probed.
FilesSave::ticketFolder($conn, $analystId, $parentId, $ticketId)
// "ABC-123 - Printer jammed" (ticket number, " - ", subject; cleaned, 120 characters at most)
// inside the chosen folder - REUSED when it is already there, made (inherit_permissions = 1) when notIt inherits the parent's permissions, as any folder made in Files does. Making it needs only Upload on the parent, which the save has already checked. It is audited as create_folder with for_ticket.
filesStoreLocalFile($conn, $analystId, $folderId, $localPath, $name, $onConflict)
// 'ask' and the name is taken -> ['conflict' => true, 'name', 'choices', 'default'] (the dialog asks)
// 'skip' -> ['skipped' => true]
// otherwise -> ['item_id', 'version_no', 'name']In order:
- The choice must be one Files โ Settings โ Versions offers. Overwrite needs Modify, and the size must be within the largest upload.
- The bytes are copied with
putFile($rel, $localPath, false). The source attachment stays exactly where it was. - The item and version rows are written in one transaction. If that fails, the bytes just written are deleted.
-
queueIfMoved()runs, then the audit (uploadornew_version,saved_from: freeitsm), then overwrite or keep-N.
- TRAP:
filesStoreLocalFile()andapi/files/upload.php's finish step write a file the same way. Bytes, rows,queueIfMoved, then keep-N or overwrite. Change one and you change the other. - TRAP:
FilesSaveusesfilesFreeName()andfilesNameTaken()fromincludes/files/api_bootstrap.php. That file also signs the request in, so a caller must be a Files endpoint. That is why the tests for this go through HTTP. - TRAP:
files_save.phpneedsservice_context.phpforServiceError. The first live run failed with Class "ServiceError" not found.files_attach.phponly had it by luck, throughservices/documents.php. - TRAP: in a test script,
__DIR__is the script's folder, not the install's. Use the working directory when writing fixture files into the install.
-
A live run on the test install through the real endpoints (12):
- the folder offered as a target;
- an email attachment saved, with exactly its contents, and the original untouched;
- saving again asking, with the upload's choices;
- Keep both giving
(2); - "In a folder for this ticket" making
<number> - <subject>, and reusing it next time; - an attachment that does not exist refused;
- a stored document saved;
- the panel told
can_save_to_files; - the Sharing defaults saved and read back.
-
Chrome:
- the ticket page loads
inbox.jsandsave-to-files.js; - the attachment list shows Save to Files;
- clicking it opens the dialog. With no folder the person can upload to, the dialog says so.
- the ticket page loads
See also: Attach from Files - Developer Guide ยท Files versions - Developer Guide ยท Files - Developer Guide