<?php
/**
 *
 * This file is part of HESK - PHP Help Desk Software.
 *
 * (c) Copyright Klemen Stirn. All rights reserved.
 * https://www.hesk.com
 *
 * For the full copyright and license agreement information visit
 * https://www.hesk.com/eula.php
 *
 */

/* Check if this is a valid include */
if (!defined('IN_SCRIPT')) {die('Invalid attempt');}

/***************************
Function hesk_uploadFiles()
***************************/
function hesk_uploadFile($i)
{
	global $hesk_settings, $hesklang, $trackingID, $hesk_error_buffer;

	/* Return if name is empty */
	if (empty($_FILES['attachment']['name'][$i])) {return '';}

    /* Parse the name */
	$file_realname = hesk_cleanFileName($_FILES['attachment']['name'][$i]);

	/* Check file extension */
	$ext = strtolower(strrchr($file_realname, "."));
	if ( ! in_array($ext,$hesk_settings['attachments']['allowed_types']))
	{
        return hesk_fileError(sprintf($hesklang['type_not_allowed'], $ext, $file_realname));
	}

	/* Check file size */
	if ($_FILES['attachment']['size'][$i] > $hesk_settings['attachments']['max_size'])
	{
	    return hesk_fileError(sprintf($hesklang['file_too_large'], $file_realname));
	}
	else
	{
	    $file_size = $_FILES['attachment']['size'][$i];
	}

	/* Generate a random file name */
    $file_name = hesk_generateAttachmentName($file_realname, $ext, $trackingID);

    // Does the temporary file exist? If not, probably server-side configuration limits have been reached
    // Uncomment this for debugging purposes
    /*
    if ( ! file_exists($_FILES['attachment']['tmp_name'][$i]) )
    {
		return hesk_fileError($hesklang['fnuscphp']);
    }
    */

	/* If upload was successful let's create the headers */
	if ( ! move_uploaded_file($_FILES['attachment']['tmp_name'][$i], dirname(dirname(__FILE__)).'/'.$hesk_settings['attach_dir'].'/'.$file_name))
	{
	    return hesk_fileError($hesklang['cannot_move_tmp']);
	}

	$info = array(
	    'saved_name'=> $file_name,
	    'real_name' => $file_realname,
	    'size'      => $file_size
	);

	return $info;
} // End hesk_uploadFile()

function hesk_generateAttachmentName($file_realname, $ext, $tracking_id = '') {
    /* Generate a random file name */
    $useChars='AEUYBDGHJLMNPQRSTVWXZ123456789';
    $tmp = uniqid();
    for($j=1;$j<10;$j++) {
        $tmp .= $useChars[mt_rand(0,29)];
    }

    if (defined('KB') || $tracking_id === '') {
        return substr(md5($tmp . $file_realname), 0, 200) . $ext;
    }

    return substr($tracking_id . '_' . md5($tmp . $file_realname), 0, 200) . $ext;
}

function hesk_uploadTempFile() {
    global $hesk_settings, $hesklang;

    /* Return if name is empty */
    if (empty($_FILES['attachment']['name'])) {
        return null;
    }

    /* Parse the name */
    $file_realname = hesk_cleanFileName($_FILES['attachment']['name']);

    /* Check file extension */
    $ext = strtolower(strrchr($file_realname, "."));
    if (!in_array($ext,$hesk_settings['attachments']['allowed_types'])) {
        return array(
            'status' => 'failure',
            'status_code' => 400,
            'message' => sprintf($hesklang['type_not_allowed'], $ext, $file_realname)
        );
    }

    /* Check file size */
    if ($_FILES['attachment']['size'] > $hesk_settings['attachments']['max_size']) {
        return array(
            'status' => 'failure',
            'status_code' => 400,
            'message' => sprintf($hesklang['file_too_large'], $file_realname)
        );
    } else {
        $file_size = $_FILES['attachment']['size'];
    }

    /* Check for potential attachment flooding */
    $ip = hesk_getClientIP();
    if (hesk_attachmentFloodingDetected($ip)) {
        return array(
            'status' => 'failure',
            'status_code' => 429,
            'message' => $hesklang['attachment_too_many_uploads']
        );
    }

    $file_name = hesk_generateAttachmentName($file_realname, $ext);

    // Does the temporary file exist? If not, probably server-side configuration limits have been reached
    // Uncomment this for debugging purposes
    /*
    if ( ! file_exists($_FILES['attachment']['tmp_name']) )
    {
		return hesk_fileError($hesklang['fnuscphp']);
    }
    */

    $hesk_settings['server_path'] = dirname(dirname(__FILE__)).'/'.$hesk_settings['attach_dir'].'/temp/';
    if (!is_dir($hesk_settings['server_path'])) {
        @mkdir($hesk_settings['server_path']);
        @file_put_contents($hesk_settings['server_path'].'index.htm', '');
    }

    /* If upload was successful let's create the headers */
    ob_start();
    if (!move_uploaded_file($_FILES['attachment']['tmp_name'], $hesk_settings['server_path'].$file_name))
    {
        ob_end_clean();
        return array(
            'status' => 'failure',
            'status_code' => 500,
            'message' => $hesklang['error'] . ': ' . $hesklang['cannot_move_tmp']
        );
    }
    ob_end_clean();

    // Generate a random ID to use when deleting temporary attachments
    $unique_id = uniqid(rand(), true);

    hesk_dbQuery("INSERT INTO `".hesk_dbEscape($hesk_settings['db_pfix'])."temp_attachments` (`saved_name`, `unique_id`, `real_name`, `expires_at`, `size`)
    VALUES ('".hesk_dbEscape($file_name)."', '".hesk_dbEscape($unique_id)."', '".hesk_dbEscape($file_realname)."', NOW() + INTERVAL 3 HOUR, ".intval($file_size).")");

    // Increment limits used for IP
    hesk_dbQuery("INSERT INTO `".hesk_dbEscape($hesk_settings['db_pfix'])."temp_attachments_limits` (`ip`,`upload_count`)
        VALUES ('".hesk_dbEscape(hesk_getClientIP())."', 1) ON DUPLICATE KEY UPDATE `upload_count` = `upload_count` + 1");

    $info = array(
        'status' => 'success',
        'status_code' => 200,
        'file_key'=> $unique_id
    );

    return $info;
} // End hesk_uploadTempFile

function hesk_attachmentFloodingDetected($ip) {
    global $hesk_settings;

    // Reset counters for any IPs that haven't uploaded in an hour
    hesk_dbEscape("DELETE FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."temp_attachments_limits` WHERE `last_upload_at` < (NOW() - INTERVAL 1 HOUR)");

    $res = hesk_dbQuery("SELECT `upload_count` FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."temp_attachments_limits` WHERE `ip` = '".hesk_dbEscape($ip)."'");
    if (hesk_dbNumRows($res) < 1) {
        return false;
    }
    $row = hesk_dbFetchAssoc($res);

    // Change "100" to whatever max amount is appropriate.
    return $row['upload_count'] > 100;
}

function hesk_fileError($error)
{
	global $hesk_settings, $hesklang, $trackingID;
    global $hesk_error_buffer;

	$hesk_error_buffer['attachments'] = $error;

	return false;
} // End hesk_fileError()


function hesk_removeAttachments($attachments)
{
	global $hesk_settings, $hesklang;

	$hesk_settings['server_path'] = dirname(dirname(__FILE__)).'/'.$hesk_settings['attach_dir'].'/';

	foreach ($attachments as $myatt)
	{
		hesk_unlink($hesk_settings['server_path'].$myatt['saved_name']);
	}

	return true;
} // End hesk_removeAttachments()

function hesk_removeExpiredTempAttachments() {
    global $hesk_settings;

    // 1. Grab temp attachments that are expired
    $res = hesk_dbQuery("SELECT `att_id`, `saved_name` FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."temp_attachments`
        WHERE `expires_at` < NOW()");

    $hesk_settings['server_path'] = dirname(dirname(__FILE__)).'/'.$hesk_settings['attach_dir'].'/temp/';

    while ($row = hesk_dbFetchAssoc($res)) {
        hesk_unlink($hesk_settings['server_path'].$row['saved_name']);
        hesk_dbQuery("DELETE FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."temp_attachments` WHERE `att_id` = ".intval($row['att_id']));
    }
}

function hesk_migrateTempAttachments($attachments, $tracking_id = '') {
    global $hesk_settings;

    $moved_attachments = array();
    foreach ($attachments as $myatt) {
        hesk_deleteTempAttachment($myatt['file_key']);

        $old_name = $myatt['saved_name'];
        $myatt['saved_name'] = ($tracking_id !== '') ? "{$tracking_id}_{$old_name}" : $old_name;
        hesk_moveAttachment($old_name, $myatt['saved_name']);

        $moved_attachments[] = $myatt;
    }

    // Reset limits for the IP
    hesk_dbQuery("DELETE FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."temp_attachments_limits` WHERE `ip` = '".hesk_dbEscape(hesk_getClientIP())."'");

    return $moved_attachments;
}

function hesk_moveAttachment($old_name, $new_name) {
    global $hesk_settings;

    $hesk_settings['temp_server_path'] = dirname(dirname(__FILE__)).'/'.$hesk_settings['attach_dir'].'/temp/';
    $hesk_settings['server_path'] = dirname(dirname(__FILE__)).'/'.$hesk_settings['attach_dir'].'/';

    hesk_rename($hesk_settings['temp_server_path'].$old_name, $hesk_settings['server_path'].$new_name);
}

function hesk_deleteTempAttachment($file_key, $delete_file = false) {
    global $hesk_settings;

    $hesk_settings['server_path'] = dirname(dirname(__FILE__)).'/'.$hesk_settings['attach_dir'].'/temp/';

    $res = hesk_dbQuery("SELECT `att_id`, `saved_name` FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."temp_attachments`
        WHERE `unique_id` = '".hesk_dbEscape($file_key)."'");

    if ($row = hesk_dbFetchAssoc($res)) {
        if ($delete_file) {
            hesk_unlink($hesk_settings['server_path'].$row['saved_name']);
        }
        hesk_dbQuery("DELETE FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."temp_attachments` WHERE `att_id` = ".intval($row['att_id']));
    }
}

function hesk_getTemporaryAttachment($file_key) {
    global $hesk_settings;

    $rs = hesk_dbQuery("SELECT * FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "temp_attachments` WHERE `unique_id` = '" . hesk_dbEscape($file_key) . "'");
    if (hesk_dbNumRows($rs) == 0) {
        return NULL;
    }
    $row = hesk_dbFetchAssoc($rs);

    $hesk_settings['server_path'] = dirname(dirname(__FILE__)).'/'.$hesk_settings['attach_dir'].'/temp/';
    if (!file_exists($hesk_settings['server_path'].$row['saved_name'])) {
        // Not deleting the file itself because it, well, doesn't exist.
        hesk_deleteTempAttachment($file_key);
        return null;
    }

    $info = array(
        'saved_name' => $row['saved_name'],
        'real_name' => $row['real_name'],
        'size' => $row['size'],
        'file_key' => $row['unique_id']
    );

    return $info;
}

//region Dropzone
function build_dropzone_markup($admin = false, $id = 'filedrop', $startingId = 1, $show_file_limits = true) {
    global $hesklang, $hesk_settings;

    $directory_separator = $admin ? '../' : '';
    echo '<div class="dropzone dz-click-'.$id.'" id="' . $id . '">
        <div class="fallback">
            <input type="hidden" name="use-legacy-attachments" value="1">';
    for ($i = $startingId; $i <= $hesk_settings['attachments']['max_number']; $i++) {
        $cls = ($i == 1 && isset($_SESSION['iserror']) && in_array('attachments', $_SESSION['iserror'])) ? ' class="isError" ' : '';
        echo '<input type="file" name="attachment[' . $i . ']" size="50" ' . $cls . ' /><br />';
    }
    echo '</div>
    </div>
    <div class="btn btn-full fileinput-button filedropbutton-' . $id . ' dz-click-'.$id.'">' . $hesklang['attachment_add_files'] . '</div>';
    if ($show_file_limits) {
        echo '<a class="link" href="' . $directory_separator . 'file_limits.php" target="_blank"
       onclick="Javascript:hesk_window(\'' . $directory_separator . 'file_limits.php\',250,500);return false;">'. $hesklang['ful'] . '</a>';
    }

    output_attachment_id_holder_container($id);
}

function display_dropzone_field($url, $is_admin, $id = 'filedrop', $max_files_override = -1) {
    global $hesk_settings, $hesklang;

    output_dropzone_window();

    $acceptedFiles = implode(',', $hesk_settings['attachments']['allowed_types']);
    $size = round($hesk_settings['attachments']['max_size'] / 1048576, 8, PHP_ROUND_HALF_UP);
    $max_files = $max_files_override > -1 ? $max_files_override : $hesk_settings['attachments']['max_number'];

    // Let's define this function we may need if it's not defined already
    if ( ! isset($hesk_settings['HeskWithout_defined'])) {
        echo "<script>const HeskWithout = (list, rejectedItem) => list.filter((item) => item !== rejectedItem).map((item) => item);</script>";
        $hesk_settings['HeskWithout_defined'] = true;
    }

    // Dropzone auto-discovery is being removed in v6.  As such, autodiscovery is disabled here should we want to
    // upgrade in the future.
    echo "
    <script>
    var pleaseWaitMessage = ".json_encode($hesklang['please_wait']).";
    Dropzone.autoDiscover = false;
    var dropzone{$id} = new Dropzone('#{$id}', {
        paramName: 'attachment',
        url: '{$url}',
        parallelUploads: {$max_files},
        maxFiles: {$max_files},
        acceptedFiles: ".json_encode($acceptedFiles).",
        maxFilesize: {$size}, // MB
        dictDefaultMessage: ".json_encode($hesklang['attachment_viewer_message']).",
        dictFallbackMessage: '',
        dictInvalidFileType: ".json_encode($hesklang['attachment_invalid_type_message']).",
        dictResponseError: ".json_encode(defined('HESK_DEMO') ? $hesklang['ddemo'] : $hesklang['attachment_upload_error']).",
        dictFileTooBig: ".json_encode($hesklang['attachment_too_large']).",
        dictCancelUpload: ".json_encode($hesklang['attachment_cancel']).",
        dictCancelUploadConfirmation: ".json_encode($hesklang['attachment_confirm_cancel']).",
        dictRemoveFile: ".json_encode($hesklang['attachment_remove']).",
        dictMaxFilesExceeded: ".json_encode($hesklang['attachment_max_exceeded']).",
        previewTemplate: $('#previews').html(),
        clickable: '.dz-click-".$id."',
        uploadMultiple: false
    });
    
    dropzone{$id}.on('success', function(file, response) {
        var jsonResponse = JSON.parse(response);
        // console.log(JSON.stringify(jsonResponse, null, 4));

        if(jsonResponse.hasOwnProperty('status') && jsonResponse['status'] == 'failure'){
            // Upload was OK, but something failed on the server-side
            dropzone{$id}.emit('uploadprogress', file, 0);
            dropzone{$id}.files = HeskWithout(dropzone{$id}.files, file);
            dropzone{$id}.options.error(file, jsonResponse['message']);
        } else {
            // The response will only be a JSON object holding the saved and real name
            outputAttachmentIdHolder(jsonResponse['file_key'], '".$id."');

            // Add the database id to the file
            file['databaseResponse'] = jsonResponse['file_key'];
        }
    });
    dropzone{$id}.on('addedfile', function() {
        var numberOfFiles = $('#" . $id . " .file-row').length;

        var disabled = false;
        if (numberOfFiles >= " . $max_files . ") {
            disabled = true;
        }

        $('." . $id . "button-" . $id . "').attr('disabled', disabled);
    });
    dropzone{$id}.on('removedfile', function(file) {
        if (file.beingRetried) {
            return;
        }
    
        // Remove the attachment from the database and the filesystem.
        removeAttachment(".$id.", file['databaseResponse'], ".($is_admin ? "true" : "false").");

        var numberOfFiles = $('#" . $id . " .file-row').length;

        var disabled = false;
        if (numberOfFiles >= " . $max_files . ") {
            disabled = true;
        }
        $('." . $id . "button-" . $id . "').attr('disabled', disabled);
        
        dropzone{$id}.getRejectedFiles().forEach(function(file) {
            file.beingRetried = true;
            dropzone{$id}.removeFile(file);
            file.status = undefined;
            file.accepted = undefined;
            file.beingRetried = false;
            dropzone{$id}.addFile(file);
        });
    });
    dropzone{$id}.on('queuecomplete', function() {
        $('input[type=\"submit\"]').attr('disabled', false);
        if(typeof attachmentQueueComplete === 'function') {
            attachmentQueueComplete();
        }
    });
    dropzone{$id}.on('processing', function() {
        $('input[type=\"submit\"]').attr('disabled', true);
        if(typeof attachmentQueueProcessing === 'function') {
            attachmentQueueProcessing();
        }
    });
    dropzone{$id}.on('uploadprogress', function(file, percentage) {
        $(file.previewTemplate).find('#percentage').text(percentage + '%');
    });
    dropzone{$id}.on('error', function(file, message) {
        $(file.previewTemplate).addClass('alert-danger');
        
        var actualMessage = message.title + ': ' + message.message;
        if (!message.message) {
            actualMessage = message;
        }
        
        $(file.previewElement).addClass('dz-error').find('[data-dz-errormessage]').text(actualMessage);
        if(typeof attachmentError === 'function') {
            attachmentError();
        }
    });
    </script>
    ";

}

function dropzone_display_existing_files($files, $dropzone_id = 'filedrop') {
    foreach ($files as $file) {
        dropzone_display_existing_file($file['real_name'], $file['size'], $file['file_key'], $dropzone_id);
    }
}

function dropzone_display_existing_file($name, $size, $file_key, $dropzone_id = 'filedrop') {
    $uniqid = uniqid();
    $successPayload = json_encode(array(
        'file_key' => $file_key
    ));
    echo "
    <script>
    var tempFile{$uniqid} = { 
        name: ".json_encode($name).", 
        size: ".$size."
    };
    tempFile{$uniqid}.accepted = true;
    tempFile{$uniqid}.status = Dropzone.SUCCESS;
    dropzone{$dropzone_id}.files.push(tempFile{$uniqid});
    dropzone{$dropzone_id}._updateMaxFilesReachedClass();
    dropzone{$dropzone_id}.emit('addedfile', tempFile{$uniqid});
    dropzone{$dropzone_id}.emit('complete', tempFile{$uniqid});
    dropzone{$dropzone_id}.emit('success', tempFile{$uniqid}, '{$successPayload}');
    dropzone{$dropzone_id}.emit('uploadprogress', tempFile{$uniqid}, 100);
    </script>
    ";
}

function output_dropzone_window() {
    echo '
    <div id="previews" style="display:none">
        <div id="template" class="file-row">
            <!-- This is used as the file preview template -->
            <div class="attachment-row">
                <div class="name-size-delete">
                    <div class="name-size">
                        <div class="name">
                            <p class="name" data-dz-name></p>
                        </div>
                        <div class="size">
                            (<span data-dz-size></span>)
                        </div>
                    </div>
                    <div class="delete-button">
                        <svg class="icon icon-delete" data-dz-remove>
                            <use xlink:href="'.HESK_PATH.'img/sprite.svg#icon-delete"></use>
                        </svg>
                    </div>
                </div>
                <div class="upload-progress">
                    <div style="border: 1px solid #d4d6e3; width: 100%; height: 19px">
                        <div style="font-size: 1px; height: 17px; width: 0px; border: none; background-color: green" data-dz-uploadprogress>
                        </div>
                    </div>
                </div>
            </div>
            <div class="error">
                <strong class="error text-danger" data-dz-errormessage></strong>
            </div>
        </div>
    </div>';
}

function output_attachment_id_holder_container($id) {
    echo '<div id="attachment-holder-' . $id . '" class="hide"></div>';
}

//endregion
