roundcubemail/program/lib/Roundcube/rcube_uploads.php
Michael Voříšek 3e458fa5fd
Refer native constants unambiguously (#9275)
* Fix "native_constant_invocation" CS

* "self_accessor" was fixed in 9269 PR

* "php_unit_strict" was fixed in 9268 PR
2023-12-23 17:02:19 +01:00

283 lines
9.5 KiB
PHP

<?php
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| This class provides API for access to uploaded files information. |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* A trait providing access to metadata of files uploaded in a session.
*/
trait rcube_uploads
{
/**
* Get an uploaded file information.
*
* @param int $id Upload ID
*
* @return array|null Hash array with file upload metadata, NULL if not found
*/
public function get_uploaded_file($id)
{
if (!($session_id = $this->get_session_id())) {
return null;
}
$sql_result = $this->db->query(
"SELECT * FROM " . $this->db->table_name('uploads', true)
. " WHERE `session_id` = ? AND `upload_id` = ?",
$session_id, $id
);
if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
$sql_arr['id'] = $sql_arr['upload_id'];
$sql_arr += json_decode($sql_arr['metadata'], true);
unset($sql_arr['upload_id'], $sql_arr['session_id'], $sql_arr['metadata']);
return $sql_arr;
}
}
/**
* Return a list of all uploaded files (in the current session).
*
* @param string $group The upload context group
*
* @return array List of uploaded files
*/
public function list_uploaded_files($group)
{
if (!($session_id = $this->get_session_id())) {
return [];
}
$sql_result = $this->db->query(
"SELECT * FROM " . $this->db->table_name('uploads', true)
. " WHERE `session_id` = ? AND `group` = ?"
. " ORDER BY `created`",
$session_id, $group
);
$result = [];
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
$sql_arr['id'] = $sql_arr['upload_id'];
$sql_arr += json_decode($sql_arr['metadata'], true);
unset($sql_arr['upload_id'], $sql_arr['session_id'], $sql_arr['metadata']);
$result[] = $sql_arr;
}
return $result;
}
/**
* Update a specific upload record.
*
* @param int $id Upload ID
* @param array $data Hash array with col->value pairs to update
*
* @return bool True if saved successfully, false if nothing changed
*/
public function update_uploaded_file($id, $data)
{
if (!($file = $this->get_uploaded_file($id))) {
return false;
}
$metadata = $this->prepare_upload_metadata(array_merge($file, $data));
$sql = "UPDATE " . $this->db->table_name('uploads', true)
. " SET `metadata` = ?"
. " WHERE `upload_id` = ? AND `session_id` = ?";
$update = $this->db->query($sql, $metadata, $id, $this->get_session_id());
return $this->db->affected_rows($update) > 0;
}
/**
* Create a new uploaded file record in the session.
*
* @param array $data Hash array with col->value pairs to save
* It must include: id, group, name, data (or path), size.
* It can include: mimetype, charset, content_id or any other properties.
* It will be modified by the attachment's handling plugin(s).
* @param string $hook Optional plugin API hook name (attachment_upload or attachment_save)
*
* @return bool True on success or False on error
*/
public function insert_uploaded_file(&$data, $hook = null)
{
if (!($session_id = $this->get_session_id())) {
return false;
}
$data = $this->plugins->exec_hook($hook ?: 'attachment_upload', $data);
if (empty($data['status']) || !empty($data['abort'])) {
return false;
}
$metadata = $this->prepare_upload_metadata($data);
$sql = "INSERT INTO " . $this->db->table_name('uploads', true)
. " (`created`, `session_id`, `upload_id`, `group`, `metadata`)"
. " VALUES (" . $this->db->now() . ", ?, ?, ?, ?)";
$insert = $this->db->query($sql, $session_id, $data['id'], $data['group'] ?? null, $metadata);
return $this->db->affected_rows($insert) > 0;
}
/**
* Delete an upload record
*
* @param int $id Upload ID
*
* @return bool True if deleted successfully, False otherwise
*/
public function delete_uploaded_file($id)
{
if (!($session_id = $this->get_session_id())) {
return false;
}
if ($attachment = $this->get_uploaded_file($id)) {
$attachment = $this->plugins->exec_hook('attachment_delete', $attachment);
}
if (empty($attachment['status'])) {
return false;
}
$this->db->query(
"DELETE FROM " . $this->db->table_name('uploads', true)
. " WHERE `session_id` = ? AND `upload_id` = ?",
$session_id,
$id
);
return $this->db->affected_rows() > 0;
}
/**
* Delete all upload records (in the current session) by group name.
*
* @param string $group Group name
*
* @return bool True if deleted successfully, False otherwise
*/
public function delete_uploaded_files($group)
{
if (!($session_id = $this->get_session_id())) {
return false;
}
$this->plugins->exec_hook('attachments_cleanup', ['group' => $group]);
$this->db->query(
"DELETE FROM " . $this->db->table_name('uploads', true)
. " WHERE `session_id` = ? AND `group` = ?",
$session_id,
$group
);
return $this->db->affected_rows() > 0;
}
/**
* Outputs uploaded file content (with image thumbnails support)
*
* @param array $file Uploaded file data
* @param bool $thumbnail Generate and return a thumbnail image
*/
public function display_uploaded_file($file, $thumbnail = false)
{
$file = $this->plugins->exec_hook('attachment_display', $file);
if (!empty($file['status'])) {
if (empty($file['size'])) {
$file['size'] = !empty($file['data']) ? strlen($file['data']) : @filesize($file['path']);
}
// generate image thumbnail for file browser in HTML editor
if ($thumbnail) {
$thumbnail_size = 80;
$mimetype = $file['mimetype'];
$file_ident = $file['id'] . ':' . $file['mimetype'] . ':' . $file['size'];
$thumb_name = 'thumb' . md5($file_ident . ':' . $this->user->ID . ':' . $thumbnail_size);
$cache_file = rcube_utils::temp_filename($thumb_name, false, false);
// render thumbnail image if not done yet
if (!is_file($cache_file)) {
if (empty($file['path'])) {
$orig_name = $filename = $cache_file . '.tmp';
file_put_contents($orig_name, $file['data']);
}
else {
$filename = $file['path'];
}
$image = new rcube_image($filename);
if ($imgtype = $image->resize($thumbnail_size, $cache_file, true)) {
$mimetype = 'image/' . $imgtype;
if (!empty($orig_name)) {
unlink($orig_name);
}
}
}
if (is_file($cache_file)) {
// cache for 1h
$this->output->future_expire_header(3600);
header('Content-Type: ' . $mimetype);
header('Content-Length: ' . filesize($cache_file));
readfile($cache_file);
exit;
}
}
header('Content-Type: ' . $file['mimetype']);
header('Content-Length: ' . $file['size']);
if (isset($file['data']) && is_string($file['data'])) {
echo $file['data'];
}
elseif (!empty($file['path'])) {
readfile($file['path']);
}
}
}
/**
* Prepare upload metadata to store in DB
*/
protected function prepare_upload_metadata($data)
{
// Remove unwanted properties
$data = array_diff_key($data, array_fill_keys(['id', 'group', 'status', 'abort', 'error', 'data', 'created'], 1));
// Remove null values
$data = array_filter($data, static function ($v) { return $v !== null; });
// Convert to string
$data = json_encode($data, \JSON_INVALID_UTF8_IGNORE);
return $data;
}
}