<?php
/*
 * Copyright (C) 2024       Frédéric France             <frederic.france@free.fr>
 * Copyright (C) 2024		MDW							<mdeweerd@users.noreply.github.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

/**
* \file        htdocs/webportal/controllers/document.controller.class.php
* \ingroup     webportal
* \brief       This file is a controller for documents
*/

require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';


/**
 * Class for DocumentController
 */
class DocumentController extends Controller
{
	/**
	 * @var string Action
	 */
	public $action;

	/**
	 * @var	boolean	Is Attachment
	 */
	public $attachment;

	/**
	 * @var string Encoding
	 */
	public $encoding;

	/**
	 * @var int Entity
	 */
	public $entity;

	/**
	 * @var string File name
	 */
	public $filename;

	/**
	 * @var string Full path of original file
	 */
	public $fullpath_original_file;

	/**
	 * @var string Full path of original file with encoded for OS
	 */
	public $fullpath_original_file_osencoded;

	/**
	 * @var string Module of document ('module', 'module_user_temp', 'module_user' or 'module_temp'). Example: 'medias', 'invoice', 'logs', 'tax-vat', ...
	 */
	public $modulepart;

	/**
	 * @var string Relative path with filename, relative to modulepart.
	 */
	public $original_file;

	/**
	 * @var string Mime type of file
	 */
	public $type;


	/**
	 * Init
	 *
	 * @return	void
	 */
	public function init()
	{
		global $conf, $hookmanager;

		define('MAIN_SECURITY_FORCECSP', "default-src: 'none'");

		if (!defined('NOTOKENRENEWAL')) {
			define('NOTOKENRENEWAL', '1');
		}
		if (!defined('NOREQUIREMENU')) {
			define('NOREQUIREMENU', '1');
		}
		if (!defined('NOREQUIREHTML')) {
			define('NOREQUIREHTML', '1');
		}
		if (!defined('NOREQUIREAJAX')) {
			define('NOREQUIREAJAX', '1');
		}

		$context = Context::getInstance();

		$encoding = '';
		$action = GETPOST('action', 'aZ09');
		$original_file = GETPOST('file', 'alphanohtml'); // Do not use urldecode here ($_GET are already decoded by PHP).
		$modulepart = GETPOST('modulepart', 'alpha');
		$entity = GETPOSTINT('entity') ? GETPOSTINT('entity') : $conf->entity;
		$socId = GETPOSTINT('soc_id');

		// Security check
		if (empty($modulepart)) {
			httponly_accessforbidden('Bad link. Bad value for parameter modulepart', 400);
		}
		if (empty($original_file)) {
			httponly_accessforbidden('Bad link. Missing identification to find file (original_file)', 400);
		}

		// get original file
		$ecmfile = '';

		// Define attachment (attachment=true to force choice popup 'open'/'save as')
		$attachment = true;
		if (preg_match('/\.(html|htm)$/i', $original_file)) {
			$attachment = false;
		}
		if (GETPOSTISSET("attachment")) {
			$attachment = GETPOST("attachment", 'alpha') ? true : false;
		}
		if (getDolGlobalString('MAIN_DISABLE_FORCE_SAVEAS')) {
			$attachment = false;
		}

		// Define mime type
		if (GETPOST('type', 'alpha')) {
			$type = GETPOST('type', 'alpha');
		} else {
			$type = dol_mimetype($original_file);
		}
		// Security: Force to octet-stream if file is a dangerous file. For example when it is a .noexe file
		// We do not force if file is a javascript to be able to get js from website module with <script src="
		// Note: Force whatever is $modulepart seems ok.
		if (!in_array($type, array('text/x-javascript')) && !dolIsAllowedForPreview($original_file)) {
			$type = 'application/octet-stream';
		}

		// Security: Delete string ../ or ..\ into $original_file
		$original_file = preg_replace('/\.\.+/', '..', $original_file);    // Replace '... or more' with '..'
		$original_file = str_replace('../', '/', $original_file);
		$original_file = str_replace('..\\', '/', $original_file);

		// Check security and set return info with full path of file
		$accessallowed = 0; // not allowed by default
		$moduleName = $modulepart;
		$moduleNameEn = $moduleName;
		if ($moduleName == 'commande') {
			$moduleNameEn = 'order';
		} elseif ($moduleName == 'facture') {
			$moduleNameEn = 'invoice';
		}
		$moduleNameUpperEn = strtoupper($moduleNameEn);
		// check config access
		// and file mime type (only PDF)
		// and check login access
		if (getDolGlobalInt('WEBPORTAL_' . $moduleNameUpperEn . '_LIST_ACCESS')
			&& in_array($type, array('application/pdf'))
			&& ($context->logged_thirdparty && $context->logged_thirdparty->id > 0)
			&& $context->logged_thirdparty->id == $socId
		) {
			if (isModEnabled($moduleName) && isset($conf->{$moduleName}->multidir_output[$entity])) {
				$original_file = $conf->{$moduleName}->multidir_output[$entity] . '/' . $original_file;
				$accessallowed = 1;
			}
		}
		$fullpath_original_file = $original_file; // $fullpath_original_file is now a full path name

		// Security:
		// Limit access if permissions are wrong
		if (!$accessallowed) {
			accessforbidden();
		}

		// Security:
		// We refuse directory transversal change and pipes in file names
		if (preg_match('/\.\./', $fullpath_original_file) || preg_match('/[<>|]/', $fullpath_original_file)) {
			dol_syslog("Refused to deliver file " . $fullpath_original_file);
			print "ErrorFileNameInvalid: " . dol_escape_htmltag($original_file);
			exit;
		}

		// Find the subdirectory name as the reference
		$refname = basename(dirname($original_file) . "/");

		$filename = basename($fullpath_original_file);
		$filename = preg_replace('/\.noexe$/i', '', $filename);

		// Output file on browser
		dol_syslog("document controller download $fullpath_original_file filename=$filename content-type=$type");
		$fullpath_original_file_osencoded = dol_osencode($fullpath_original_file); // New file name encoded in OS encoding charset

		// This test if file exists should be useless. We keep it to find bug more easily
		if (!file_exists($fullpath_original_file_osencoded)) {
			dol_syslog("ErrorFileDoesNotExists: " . $fullpath_original_file);
			print "ErrorFileDoesNotExists: " . $original_file;
			exit;
		}

		$fileSize = dol_filesize($fullpath_original_file); // the returned value is in byte
		$fileSizeMaxDefault = 20 * 1024 * 1024; // 20 Mo by default
		$fileSizeMax = getDolGlobalInt('MAIN_SECURITY_MAXFILESIZE_DOWNLOADED', $fileSizeMaxDefault);
		if ($fileSize > $fileSizeMax) {
			dol_syslog('ErrorFileSizeTooLarge: ' . $fileSize);
			print 'ErrorFileSizeTooLarge: ' . $fileSize . ' (max ' . $fileSizeMax . ')';
			exit;
		}

		// Hooks
		$hookmanager->initHooks(array('document'));
		$parameters = array('ecmfile' => $ecmfile, 'modulepart' => $modulepart, 'original_file' => $original_file,
			'entity' => $entity, 'refname' => $refname, 'fullpath_original_file' => $fullpath_original_file,
			'filename' => $filename, 'fullpath_original_file_osencoded' => $fullpath_original_file_osencoded);
		$object = new stdClass();
		$reshook = $hookmanager->executeHooks('downloadDocument', $parameters, $object, $action); // Note that $action and $object may have been
		if ($reshook < 0) {
			$errors = $hookmanager->error . (is_array($hookmanager->errors) ? (!empty($hookmanager->error) ? ', ' : '') . implode(', ', $hookmanager->errors) : '');
			dol_syslog("document.php - Errors when executing the hook 'downloadDocument' : " . $errors);
			print "ErrorDownloadDocumentHooks: " . $errors;
			exit;
		}

		$this->action = $action;
		$this->attachment = $attachment;
		$this->encoding = $encoding;
		$this->entity = $entity;
		$this->filename = $filename;
		$this->fullpath_original_file = $fullpath_original_file;
		$this->fullpath_original_file_osencoded = $fullpath_original_file_osencoded;
		$this->modulepart = $modulepart;
		$this->original_file = $original_file;
		$this->type = $type;
	}

	/**
	 * Check current access to controller
	 *
	 * @return  bool
	 */
	public function checkAccess()
	{
		$this->accessRight = true;

		return parent::checkAccess();
	}

	/**
	 * Action method is called before html output
	 * can be used to manage security and change context
	 *
	 * @return  int     Return integer < 0 on error, > 0 on success
	 */
	public function action()
	{
		$context = Context::getInstance();
		if (!$context->controllerInstance->checkAccess()) {
			return -1;
		}

		//$context = Context::getInstance();
		//$context->title = $langs->trans('WebPortalDocumentTitle');
		//$context->desc = $langs->trans('WebPortalDocumentDesc');
		//$context->doNotDisplayHeaderBar=1;// hide default header

		$this->init();

		return 1;
	}

	/**
	 * Display
	 *
	 * @return  void
	 */
	public function display()
	{
		$context = Context::getInstance();
		if (!$context->controllerInstance->checkAccess()) {
			$this->display404();
			return;
		}

		// initialize
		$attachment = $this->attachment;
		$encoding = $this->encoding;
		$filename = $this->filename;
		$fullpath_original_file = $this->fullpath_original_file;
		$fullpath_original_file_osencoded = $this->fullpath_original_file_osencoded;
		$type = $this->type;

		clearstatcache();

		// Permissions are ok and file found, so we return it
		top_httphead($type);
		header('Content-Description: File Transfer');
		if ($encoding) {
			header('Content-Encoding: ' . $encoding);
		}
		// Add MIME Content-Disposition from RFC 2183 (inline=automatically displayed, attachment=need user action to open)
		if ($attachment) {
			header('Content-Disposition: attachment; filename="' . $filename . '"');
		} else {
			header('Content-Disposition: inline; filename="' . $filename . '"');
		}
		header('Cache-Control: Public, must-revalidate');
		header('Pragma: public');

		// Send file now
		header('Content-Length: ' . dol_filesize($fullpath_original_file));
		readfileLowMemory($fullpath_original_file_osencoded);
	}
}
