mirror of
https://github.com/Dolibarr/dolibarr.git
synced 2025-12-07 18:18:18 +01:00
* NEW: Rework of the management of the card and fields on the web portal * Correction pre-commit check * Correction affichage logo login * Ajout hook * Ajout params fonction FormWebPortal::convertAllLink() * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction phpstan * Correction travis * Correction travis * Correction travis * Correction travis * Correction * Fix get options of sellist by AJAX in webportal scope * Correction pre-commit * Correction pre-commit * Add hook and change hook name for viewImage controller * Correction phan * Corrections * Corrections
474 lines
16 KiB
PHP
474 lines
16 KiB
PHP
<?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/viewimage.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 ViewImageController
|
|
*/
|
|
class ViewImageController extends Controller
|
|
{
|
|
/**
|
|
* @var string Action
|
|
*/
|
|
public $action;
|
|
|
|
/**
|
|
* @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, $dolibarr_nocache, $user;
|
|
|
|
define('MAIN_SECURITY_FORCECSP', "default-src: 'none'");
|
|
|
|
if (!defined('NOREQUIRESOC')) {
|
|
define('NOREQUIRESOC', '1');
|
|
}
|
|
if (!defined('NOREQUIRETRAN')) {
|
|
define('NOREQUIRETRAN', '1');
|
|
}
|
|
if (!defined('NOCSRFCHECK')) {
|
|
define('NOCSRFCHECK', '1');
|
|
}
|
|
if (!defined('NOTOKENRENEWAL')) {
|
|
define('NOTOKENRENEWAL', '1');
|
|
}
|
|
if (!defined('NOREQUIREMENU')) {
|
|
define('NOREQUIREMENU', '1');
|
|
}
|
|
if (!defined('NOREQUIREHTML')) {
|
|
define('NOREQUIREHTML', '1');
|
|
}
|
|
if (!defined('NOREQUIREAJAX')) {
|
|
define('NOREQUIREAJAX', '1');
|
|
}
|
|
|
|
// For MultiCompany module.
|
|
// Do not use GETPOST here, function is not defined and define must be done before including main.inc.php
|
|
// Because 2 entities can have the same ref.
|
|
$entity = (!empty($_GET['entity']) ? (int) $_GET['entity'] : (!empty($_POST['entity']) ? (int) $_POST['entity'] : 1));
|
|
define("DOLENTITY", $entity);
|
|
|
|
$context = Context::getInstance();
|
|
|
|
$action = GETPOST('action', 'aZ09');
|
|
$original_file = GETPOST('file', 'alphanohtml');
|
|
$hashp = GETPOST('hashp', 'aZ09', 1);
|
|
$extname = GETPOST('extname', 'alpha', 1);
|
|
$modulepart = GETPOST('modulepart', 'alpha', 1);
|
|
$urlsource = GETPOST('urlsource', 'alpha');
|
|
$entity = (GETPOSTINT('entity') ? GETPOSTINT('entity') : $conf->entity);
|
|
|
|
// Security check
|
|
if (empty($modulepart) && empty($hashp)) {
|
|
httponly_accessforbidden('Bad link. Bad value for parameter modulepart', 400);
|
|
}
|
|
if (empty($original_file) && empty($hashp) && $modulepart != 'barcode') {
|
|
httponly_accessforbidden('Bad link. Missing identification to find file (param file or hashp)', 400);
|
|
}
|
|
if ($modulepart == 'fckeditor') {
|
|
$modulepart = 'medias'; // For backward compatibility
|
|
}
|
|
|
|
$cachestring = GETPOST("cache", 'aZ09'); // May be 1, or an int, or a hash
|
|
if ($cachestring) {
|
|
// Important: The following code is to avoid a page request by the browser and PHP CPU at each Dolibarr page access.
|
|
// We are here when param cache=xxx to force a cache policy:
|
|
// xxx=1 means cache of 3600s
|
|
// xxx=abcdef or 123456789 means a cache of 1 week (the key will be modified to get break cache use)
|
|
if (empty($dolibarr_nocache)) {
|
|
$delaycache = ((is_numeric($cachestring) && (int) $cachestring > 1 && (int) $cachestring < 999999) ? $cachestring : '3600');
|
|
header('Cache-Control: max-age=' . $delaycache . ', public, must-revalidate');
|
|
header('Pragma: cache'); // This is to avoid to have Pragma: no-cache set by proxy or web server
|
|
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + (int) $delaycache) . ' GMT'); // This is to avoid to have Expires set by proxy or web server
|
|
} else {
|
|
// If any cache on files were disable by config file (for test purpose)
|
|
header('Cache-Control: no-cache');
|
|
}
|
|
//print $dolibarr_nocache; exit;
|
|
}
|
|
|
|
// Define mime type
|
|
$type = 'application/octet-stream';
|
|
if (GETPOST('type', 'alpha')) {
|
|
$type = GETPOST('type', 'alpha');
|
|
} else {
|
|
$type = dol_mimetype($original_file);
|
|
}
|
|
|
|
// Security: This wrapper is for images. We do not allow type/html
|
|
if (preg_match('/html/i', $type)) {
|
|
httponly_accessforbidden('Error: Using the image wrapper to output a file with a mime type HTML is not possible.');
|
|
}
|
|
// Security: This wrapper is for images. We do not allow files ending with .noexe
|
|
if (preg_match('/\.noexe$/i', $original_file)) {
|
|
httponly_accessforbidden('Error: Using the image wrapper to output a file ending with .noexe is not allowed.');
|
|
}
|
|
|
|
// 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);
|
|
|
|
// Find the subdirectory name as the reference
|
|
$refname = basename(dirname($original_file) . "/");
|
|
if ($refname == 'thumbs') {
|
|
// If we get the thumbs directory, we must go one step higher. For example original_file='10/thumbs/myfile_small.jpg' -> refname='10'
|
|
$refname = basename(dirname(dirname($original_file)) . "/");
|
|
}
|
|
|
|
// Check that file is allowed for view with viewimage.php
|
|
if (!empty($original_file) && !dolIsAllowedForPreview($original_file)) {
|
|
httponly_accessforbidden('This file extension is not qualified for preview', 403);
|
|
}
|
|
|
|
// Security check
|
|
if (empty($modulepart)) {
|
|
httponly_accessforbidden('Bad value for parameter modulepart', 400);
|
|
}
|
|
|
|
// When logged in a different entity, medias cannot be accessed because $conf->$module->multidir_output
|
|
// is not set on the requested entity, but they are public documents, so reset entity
|
|
if ($modulepart === 'medias' && $entity != $conf->entity) {
|
|
$conf->entity = $entity;
|
|
$conf->setValues($this->db);
|
|
}
|
|
|
|
$sqlprotectagainstexternals = '';
|
|
$fullpath_original_file = '';
|
|
$accessallowed = 0;
|
|
|
|
// Hooks
|
|
$hookmanager->initHooks(array('viewimage'));
|
|
$parameters = array('modulepart' => $modulepart, 'original_file' => &$original_file,
|
|
'sqlprotectagainstexternals' => &$sqlprotectagainstexternals, 'fullpath_original_file' => &$fullpath_original_file,
|
|
'entity' => $entity, 'accessallowed' => &$accessallowed);
|
|
$object = new stdClass();
|
|
$reshook = $hookmanager->executeHooks('accessViewImage', $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 'accessViewImage' : " . $errors);
|
|
print "ErrorViewImageHooks: " . $errors;
|
|
exit;
|
|
} elseif (empty($reshook)) {
|
|
$check_access = dol_check_secure_access_document($modulepart, $original_file, $entity, $user, $refname);
|
|
$accessallowed = $check_access['accessallowed'];
|
|
$sqlprotectagainstexternals = $check_access['sqlprotectagainstexternals'];
|
|
$fullpath_original_file = $check_access['original_file']; // $fullpath_original_file is now a full path name
|
|
}
|
|
|
|
if (!empty($hashp)) {
|
|
$accessallowed = 1; // When using hashp, link is public so we force $accessallowed
|
|
$sqlprotectagainstexternals = '';
|
|
} else {
|
|
// Basic protection (against external users only)
|
|
if ($user->socid > 0) {
|
|
if ($sqlprotectagainstexternals) {
|
|
$resql = $this->db->query($sqlprotectagainstexternals);
|
|
if ($resql) {
|
|
$num = $this->db->num_rows($resql);
|
|
$i = 0;
|
|
while ($i < $num) {
|
|
$obj = $this->db->fetch_object($resql);
|
|
if ($user->socid != $obj->fk_soc) {
|
|
$accessallowed = 0;
|
|
break;
|
|
}
|
|
$i++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Security:
|
|
// Limit access if permissions are wrong
|
|
if (!$accessallowed) {
|
|
accessforbidden();
|
|
}
|
|
|
|
// Security:
|
|
// On interdit les remontees de repertoire ainsi que les pipe dans les noms de fichiers.
|
|
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);
|
|
$fileSizeMax = getDolGlobalInt('MAIN_SECURITY_MAXFILESIZE_DOWNLOADED');
|
|
if ($fileSizeMax && $fileSize > $fileSizeMax) {
|
|
dol_syslog('ErrorFileSizeTooLarge: ' . $fileSize);
|
|
print 'ErrorFileSizeTooLarge: ' . $fileSize . ' (max ' . $fileSizeMax . ' Kb)';
|
|
exit;
|
|
}
|
|
|
|
// Hooks
|
|
$hookmanager->initHooks(array('document'));
|
|
$parameters = array('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('viewImage', $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 'viewImage' : " . $errors);
|
|
print "ErrorViewImageHooks: " . $errors;
|
|
exit;
|
|
}
|
|
|
|
$this->action = $action;
|
|
$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()
|
|
{
|
|
global $conf;
|
|
|
|
$context = Context::getInstance();
|
|
if (!$context->controllerInstance->checkAccess()) {
|
|
$this->display404();
|
|
return;
|
|
}
|
|
|
|
// initialize
|
|
$modulepart = $this->modulepart;
|
|
$fullpath_original_file = $this->fullpath_original_file;
|
|
$fullpath_original_file_osencoded = $this->fullpath_original_file_osencoded;
|
|
$type = $this->type;
|
|
|
|
if ($modulepart == 'barcode') {
|
|
$generator = GETPOST("generator", "aZ09");
|
|
$encoding = GETPOST("encoding", "aZ09");
|
|
$readable = GETPOST("readable", 'aZ09') ? GETPOST("readable", "aZ09") : "Y";
|
|
if (in_array($encoding, array('EAN8', 'EAN13'))) {
|
|
$code = GETPOST("code", 'alphanohtml');
|
|
} else {
|
|
$code = GETPOST("code", 'restricthtml'); // This can be rich content (qrcode, datamatrix, ...)
|
|
}
|
|
|
|
// If $code is virtualcard_xxx_999.vcf, it is a file to read to get code
|
|
$reg = array();
|
|
if (preg_match('/^virtualcard_([^_]+)_(\d+)\.vcf$/', $code, $reg)) {
|
|
$vcffile = '';
|
|
$id = 0;
|
|
$login = '';
|
|
if ($reg[1] == 'user' && (int) $reg[2] > 0) {
|
|
$vcffile = $conf->user->dir_temp . '/' . $code;
|
|
$id = (int) $reg[2];
|
|
$tmpuser = new User($this->db);
|
|
$tmpuser->fetch($id);
|
|
$login = $tmpuser->login;
|
|
} elseif ($reg[1] == 'contact' && (int) $reg[2] > 0) {
|
|
$vcffile = $conf->contact->dir_temp . '/' . $code;
|
|
$id = (int) $reg[2];
|
|
}
|
|
|
|
$code = '';
|
|
if ($vcffile && $id) {
|
|
// Case of use of viewimage to get the barcode for user pubic profile,
|
|
// we must check the securekey that protet against forging url
|
|
if ($reg[1] == 'user' && (int) $reg[2] > 0) {
|
|
$encodedsecurekey = dol_hash($conf->file->instance_unique_id . 'uservirtualcard' . $id . '-' . $login, 'md5');
|
|
if ($encodedsecurekey != GETPOST('securekey')) {
|
|
$code = 'badvalueforsecurekey';
|
|
}
|
|
}
|
|
if (empty($code)) {
|
|
$code = file_get_contents($vcffile);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (empty($generator) || empty($encoding)) {
|
|
print 'Error: Parameter "generator" or "encoding" not defined';
|
|
exit;
|
|
}
|
|
|
|
$dirbarcode = array_merge(array("/core/modules/barcode/doc/"), $conf->modules_parts['barcode']);
|
|
|
|
$result = 0;
|
|
|
|
foreach ($dirbarcode as $reldir) {
|
|
$dir = dol_buildpath($reldir, 0);
|
|
$newdir = dol_osencode($dir);
|
|
|
|
// Check if directory exists (we do not use dol_is_dir to avoid loading files.lib.php)
|
|
if (!is_dir($newdir)) {
|
|
continue;
|
|
}
|
|
|
|
$result = @include_once $newdir . $generator . '.modules.php';
|
|
if ($result) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Load barcode class
|
|
$classname = "mod" . ucfirst($generator);
|
|
|
|
$module = new $classname($this->db);
|
|
'@phan-var-force ModeleBarCode $module';
|
|
/** @var ModeleBarCode $module */
|
|
if ($module->encodingIsSupported($encoding)) {
|
|
$result = $module->buildBarCode($code, $encoding, $readable);
|
|
}
|
|
} else {
|
|
// Open and return file
|
|
clearstatcache();
|
|
|
|
$filename = basename($fullpath_original_file);
|
|
|
|
// Output files on browser
|
|
dol_syslog("viewimage.php return file $fullpath_original_file filename=$filename content-type=$type");
|
|
|
|
if (!dol_is_file($fullpath_original_file) && !GETPOSTINT("noalt", 1)) {
|
|
// This test is to replace error images with a nice "notfound image" when image is not available (for example when thumbs not yet generated).
|
|
$fullpath_original_file = Context::getRootConfigUrl() . 'img/nophoto.png';
|
|
/*$error='Error: File '.$_GET["file"].' does not exists or filesystems permissions are not allowed';
|
|
print $error;
|
|
exit;*/
|
|
}
|
|
|
|
// Permissions are ok and file found, so we return it
|
|
if ($type) {
|
|
top_httphead($type);
|
|
header('Content-Disposition: inline; filename="' . basename($fullpath_original_file) . '"');
|
|
} else {
|
|
top_httphead('image/png');
|
|
header('Content-Disposition: inline; filename="' . basename($fullpath_original_file) . '"');
|
|
}
|
|
|
|
$fullpath_original_file_osencoded = dol_osencode($fullpath_original_file);
|
|
|
|
readfile($fullpath_original_file_osencoded);
|
|
}
|
|
}
|
|
}
|