dolibarr/htdocs/core/tpl/filemanager.tpl.php
Frédéric FRANCE 585afa14de
Qual: Use dolBuildUrl() instead of manual urlencode() concatenation in ecm (#39557)
* Qual: Use dolBuildUrl() instead of manual urlencode() concatenation in ecm

Replaces manual '?key='.urlencode($val).'&key2='.urlencode($val2)
string-building with dolBuildUrl($path, $params) across the ECM
directory-tree/file-manager code, for consistency with the rest of
the codebase (see htdocs/core/lib/ecm.lib.php, which already uses
this pattern for the same page) and to benefit from dolBuildUrl()'s
buildurl hook.

- core/ajax/ajaxdirtree.php: the dir_card.php edit link built from
  the sql tree loop.
- ecm/dir_card.php: the edit/add-section action buttons and the two
  delete confirmation URLs. Also switches the three buttons that used
  to manually concatenate '&token='.newToken() to dolBuildUrl()'s own
  $addtoken parameter.
- ecm/class/ecmfiles.class.php: EcmFiles::getNomUrl()'s document.php
  and file_card.php URLs.
- ecm/tpl/enablefiletreeajax.tpl.php: the ajaxdirtree.php script URL
  and the ajaxdirpreview.php URL. The token here intentionally stays
  currentToken() (not dolBuildUrl()'s own newToken()-based
  $addtoken), per the existing comment: ajaxdirtree.php has
  NOTOKENRENEWAL defined, so the token must match the one already
  valid on the calling page. $paramwithoutsection is a pre-built raw
  query-string fragment from an external caller and is appended as-is
  after the dolBuildUrl() result rather than folded into it.

Verified all five refactored URL-building expressions produce byte-
identical output to the original code for representative inputs
(including values with '/', '&' and spaces), except for query
parameter order (which has no effect) and one real, minor pre-
existing bug this incidentally fixes: the delete-section confirm URL
in dir_card.php was building '&module='.$module without urlencode(),
now correctly encoded by dolBuildUrl()/http_build_query().

Could not do a live browser check (no Chrome available for Playwright
in this environment) - verified via php -l, phpcs, and a standalone
script comparing old vs new output for each call site instead.

* Qual: Use dolBuildUrl() instead of manual urlencode() concatenation in filemanager.tpl.php

Same refactor as the previous commit, applied to the 7 remaining
manually-concatenated URLs in core/tpl/filemanager.tpl.php (used by
the ECM/medias file manager): the delete-file/delete-section/
convert-to-webp confirm URLs, the create-directory and refresh-list
toolbar buttons (now using dolBuildUrl()'s $addtoken instead of a
manual '&token='.newToken()), the two generate-webp buttons, and the
"Root" link.

$websitekeyandpageid is kept as a helper to build the raw sub-query
string embedded once (single-encoded) as the create-directory
button's 'backtopage' value - it is not itself passed to dolBuildUrl.

Verified all 7 refactored URL-building expressions produce the same
query parameters as the original code for representative inputs
(compared as parsed, order-independent query strings, since
http_build_query() does not preserve insertion order the same way as
the original manual concatenation), including one case
(convertimgwebp confirm with sortfield/sortorder) where the original
code had a harmless but sloppy leading '?&' that dolBuildUrl() no
longer produces.

* fix

* Qual: Use dolBuildUrl() instead of manual urlencode() concatenation in index_auto.php

Same refactor as the previous commits, applied to the 4 manually-
concatenated URLs in ecm/index_auto.php: the delete-file and
delete-section confirm URLs, the refresh-list toolbar link, and the
per-directory link in the auto-directories list.

Verified all 4 refactored URL-building expressions produce the same
query parameters as the original code for representative inputs
(including an empty-module/empty-section case for the refresh link,
and values with '/' and spaces for the others).

* Qual: Use dolBuildUrl() instead of manual urlencode() concatenation in ecm (file_card, dir_add_card, index_medias)

Same refactor as the previous commits, applied to the remaining
manually-concatenated URLs in:
- ecm/file_card.php: the cancel and rename-file redirects, the
  internal download link (document.php), the delete-file confirm URL
  and the edit button.
- ecm/dir_add_card.php: the delete-section confirm URL and the delete
  button (now using dolBuildUrl()'s $addtoken instead of a manual
  '&token='.newToken()).
- ecm/index_medias.php: the $backtopage URL used by
  core/actions_linkedfiles.inc.php after a confirm_deletefile.

Left ecm/search.php's '$param = "&section=".urlencode($section)'
alone: it is a raw query-string fragment (starting with '&', no
leading path) passed into FormFile::list_of_documents(), not a
base+params URL build, so it does not fit the dolBuildUrl($path,
$params) shape - same reasoning as $paramwithoutsection in the
already-refactored enablefiletreeajax.tpl.php.

Verified all 8 refactored URL-building expressions produce the same
query parameters as the original code for representative inputs
(order-independent comparison, since http_build_query() does not
preserve the original insertion order).
2026-08-17 04:41:05 +02:00

445 lines
16 KiB
PHP

<?php
/* Copyright (C) 2017 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024-2026 Frédéric France <frederic.france@free.fr>
*
* 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/>.
*
* Output code for the filemanager
* $module must be defined ('ecm', 'medias', ...)
* $formalreadyopen can be set to 1 to avoid to open the <form> to submit files a second time
*/
/**
* @var Conf $conf
* @var DoliDB $db
* @var Form $form
* @var HookManager $hookmanager
* @var Translate $langs
* @var User $user
*
* @var EcmDirectory $ecmdir
* @var Website $website
*
* @var string $action
* @var string $sortfield
* @var string $sortorder
* @var string $url
* @var ?string $module
* @var int $section
* @var string $filepathnoext
* @var string $pageid
* @var int $formalreadyopen
* @var string $websitekey
*/
// Protection to avoid direct call of template
if (empty($conf) || !is_object($conf)) {
print "Error, template page filemanager.tpl.php can't be called as URL";
exit;
}
'
@phan-var-force Website $website
@phan-var-force string $filepathnoext
@phan-var-force string $pageid
@phan-var-force EcmDirectory $ecmdir
@phan-var-force ?string $module
@phan-var-force int $section
@phan-var-force string $websitekey
';
?>
<!-- BEGIN PHP TEMPLATE core/tpl/filemanager.tpl.php -->
<!-- Doc of fileTree plugin at https://www.abeautifulsite.net/jquery-file-tree -->
<?php
require_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmdirectory.class.php';
$langs->load("ecm");
if (!isset($module)) {
$module = 'ecm';
}
$permtoadd = 0;
$permtoupload = 0;
$showroot = 0;
$error = 0;
if ($module == 'ecm') {
$permtoadd = $user->hasRight("ecm", "setup");
$permtoupload = $user->hasRight("ecm", "upload");
$showroot = 0;
}
if ($module == 'medias') {
$permtoadd = $user->hasRight("website", "write");
$permtoupload = $user->hasRight("website", "write");
$showroot = 1;
}
if (!isset($section)) {
$section = 0;
}
// Confirm remove file (for non javascript users)
if (($action == 'delete' || $action == 'file_manager_delete') && empty($conf->use_javascript_ajax)) {
// TODO Add website, pageid, filemanager if defined
print $form->formconfirm(dolBuildUrl($_SERVER["PHP_SELF"], array('section' => $section, 'urlfile' => GETPOST("urlfile"))), $langs->trans('DeleteFile'), $langs->trans('ConfirmDeleteFile'), 'confirm_deletefile', '', '', 1);
}
// Start container of all panels
?>
<!-- Begin div id="containerlayout" -->
<div id="containerlayout">
<div id="ecm-layout-north" class="toolbar largebutton">
<?php
// Start top panel, toolbar
print '<div class="inline-block toolbarbutton centpercent">';
// Toolbar
if ($permtoadd) {
$websitekeyandpageid = (!empty($websitekey) ? '&website='.urlencode($websitekey) : '').(!empty($pageid) ? '&pageid='.urlencode((string) $pageid) : '');
$paramscreatedir = array('action' => 'create', 'module' => $module);
if (!empty($websitekey)) {
$paramscreatedir['website'] = $websitekey;
}
if (!empty($pageid)) {
$paramscreatedir['pageid'] = $pageid;
}
$paramscreatedir['backtopage'] = $_SERVER["PHP_SELF"].'?file_manager=1'.$websitekeyandpageid;
print '<a id="acreatedir" href="'.dolBuildUrl(DOL_URL_ROOT.'/ecm/dir_add_card.php', $paramscreatedir).'" class="inline-block valignmiddle toolbarbutton paddingtop" title="'.dol_escape_htmltag($langs->trans('ECMAddSection')).'">';
print img_picto('', 'folder-plus', '', 0, 0, 0, '', 'size15x marginrightonly');
print '</a>';
} else {
print '<a id="acreatedir" href="#" class="inline-block valignmiddle toolbarbutton paddingtop" title="'.$langs->trans("NotAllowed").'">';
print img_picto('', 'folder-plus', 'disabled', 0, 0, 0, '', 'size15x marginrightonly');
print '</a>';
}
if ($module == 'ecm') {
if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_ECM_DISABLE_JS')) {
$tmpurl = '#';
} else {
$paramsrefresh = array('action' => 'refreshmanual', 'module' => $module);
if ($section) {
$paramsrefresh['section'] = $section;
}
$tmpurl = dolBuildUrl($_SERVER["PHP_SELF"], $paramsrefresh);
}
print '<a id="arefreshbutton" href="'.$tmpurl.'" class="inline-block valignmiddle toolbarbutton paddingtop" title="'.dol_escape_htmltag($langs->trans('ReSyncListOfDir')).'">';
print img_picto('', 'refresh', 'id="refreshbutton"', 0, 0, 0, '', 'size15x marginrightonly');
print '</a>';
}
if ($permtoadd && GETPOSTISSET('website')) { // If on file manager to manage medias of a web site
// @phan-suppress-next-line PhanTypeExpectedObjectPropAccess
print '<a id="agenerateimgwebp" href="'.dolBuildUrl($_SERVER["PHP_SELF"], array('action' => 'confirmconvertimgwebp', 'website' => $website->ref), true).'" class="inline-block valignmiddle toolbarbutton paddingtop" title="'.dol_escape_htmltag($langs->trans("GenerateImgWebp")).'">';
print img_picto('', 'images', '', 0, 0, 0, '', 'size15x flip marginrightonly');
print '</a>';
} elseif ($permtoadd && $module == 'ecm') { // If on file manager medias in ecm
if (getDolGlobalInt('ECM_SHOW_GENERATE_WEBP_BUTTON')) {
print '<a id="agenerateimgwebp" href="'.dolBuildUrl($_SERVER["PHP_SELF"], array('action' => 'confirmconvertimgwebp'), true).'" class="inline-block valignmiddle toolbarbutton paddingtop" title="'.dol_escape_htmltag($langs->trans("GenerateImgWebp")).'">';
print img_picto('', 'images', '', 0, 0, 0, '', 'size15x flip marginrightonly');
print '</a>';
}
}
print "<script>
$('#acreatedir').on('click', function() {
try{
section_dir = $('.directory.expanded')[$('.directory.expanded').length-1].children[0].rel;
section = $('.directory.expanded')[$('.directory.expanded').length-1].children[0].id.split('_')[2];
catParent = ";
if ($module == 'ecm') {
print "section;";
} else {
print "section_dir.substring(0, section_dir.length - 1);";
}
print "
} catch{
section_dir = '/';
section = 0;
catParent = ";
if ($module == 'ecm') {
print "section;";
} else {
print "section_dir;";
}
print "
}
console.log('We click to create a new directory, we set current section_dir='+section_dir+' into href url of button acreatedir');
$('#acreatedir').attr('href', $('#acreatedir').attr('href')+'%26section_dir%3D'+encodeURI(section_dir)+'%26section%3D'+encodeURI(section)+'&section_dir='+encodeURI(section_dir)+'&section='+encodeURI(section)+'&catParent='+encodeURI(catParent));
console.log($('#acreatedir').attr('href'));
});
$('#agenerateimgwebp').on('click', function() {
try{
section_dir = $('.directory.expanded')[$('.directory.expanded').length-1].children[0].rel;
section = $('.directory.expanded')[$('.directory.expanded').length-1].children[0].id.split('_')[2];
} catch{
section_dir = '/';
section = 0;
}
console.log('We click to generate webp image, we set current section_dir='+section_dir+' into href url of button agenerateimgwebp');
$('#agenerateimgwebp').attr('href', $('#agenerateimgwebp').attr('href')+'&section_dir='+encodeURI(section_dir)+'&section='+encodeURI(section));
console.log($('#agenerateimgwebp').attr('href'));
});
</script>";
// Start "Add new file" area
$nameforformuserfile = 'formuserfileecm';
print '<div class="inline-block valignmiddle floatright">';
// Zone to attach a new file
if ((!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_ECM_DISABLE_JS')) || !empty($section)) {
if ((empty($section) || $section == -1) && ($module != 'medias')) {
?>
<script>
jQuery(document).ready(function() {
jQuery('#<?php echo $nameforformuserfile ?>').hide();
});
</script>
<?php
}
$sectiondir = GETPOST('file', 'alpha') ? GETPOST('file', 'alpha') : GETPOST('section_dir', 'alpha');
print '<!-- Start form to attach new file in filemanager.tpl.php sectionid='.$section.' sectiondir='.$sectiondir.' -->'."\n";
include_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
$formfile = new FormFile($db);
print $formfile->form_attach_new_file($_SERVER["PHP_SELF"], 'none', 0, ($section ? $section : -1), $permtoupload, 48, null, '', 0, '', 0, $nameforformuserfile, '', $sectiondir, empty($formalreadyopen) ? 0 : $formalreadyopen, 0, 0, 1);
} else {
print '&nbsp;';
}
print '</div>';
// End "Add new file" area
print '</div>';
// End top panel, toolbar
?>
</div>
<div id="ecm-layout-west" class="inline-block">
<?php
// Start left area
// Ask confirmation of deletion of directory
if ($action == 'delete_section') {
print $form->formconfirm(dolBuildUrl($_SERVER["PHP_SELF"], array('section' => $section)), $langs->trans('DeleteSection'), $langs->trans('ConfirmDeleteSection', $ecmdir->label), 'confirm_deletesection', '', '', 1);
}
// End confirm
// Ask confirmation to build webp images
if ($action == 'confirmconvertimgwebp') {
$langs->load("ecm");
$section_dir = GETPOST('section_dir', 'alpha');
$section = GETPOST('section', 'alpha');
$file = GETPOST('filetoregenerate', 'alpha');
$form = new Form($db);
$formquestion = array();
$formquestion['section_dir'] = array('type' => 'hidden', 'value' => $section_dir, 'name' => 'section_dir');
$formquestion['section'] = array('type' => 'hidden', 'value' => $section, 'name' => 'section');
$formquestion['filetoregenerate'] = array('type' => 'hidden', 'value' => $file, 'name' => 'filetoregenerate');
if ($module == 'medias') {
$formquestion['website'] = array('type' => 'hidden', 'value' => $website->ref, 'name' => 'website'); // @phan-suppress-current-line PhanTypeExpectedObjectPropAccess
}
$paramsconvertimgwebp = array();
if (!empty($sortfield)) {
$paramsconvertimgwebp['sortfield'] = $sortfield;
}
if (!empty($sortorder)) {
$paramsconvertimgwebp['sortorder'] = $sortorder;
}
print $form->formconfirm(dolBuildUrl($_SERVER["PHP_SELF"], $paramsconvertimgwebp), empty($file) ? $langs->trans('ConfirmImgWebpCreation') : $langs->trans('ConfirmChosenImgWebpCreation'), empty($file) ? $langs->trans('ConfirmGenerateImgWebp') : $langs->trans('ConfirmGenerateChosenImgWebp', basename($file)), 'convertimgwebp', $formquestion, "yes", 1);
$action = 'file_manager';
}
// Duplicate images into .webp
if ($action == 'convertimgwebp' && $permtoadd) {
$file = GETPOST('filetoregenerate', 'alpha');
if ($module == 'medias') {
$imagefolder = $conf->website->dir_output.'/'.$websitekey.'/medias/'.dol_sanitizePathName(GETPOST('section_dir', 'alpha'));
} else {
$imagefolder = $conf->ecm->dir_output.'/'.dol_sanitizePathName(GETPOST('section_dir', 'alpha'));
}
include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
if (!empty($file)) {
$filelist = array();
$filelist[]["fullname"] = dol_osencode($imagefolder.'/'.$file); // get $imagefolder.'/'.$file infos
} else {
$regeximgext = getListOfPossibleImageExt();
$filelist = dol_dir_list($imagefolder, "files", 0, $regeximgext);
}
$nbconverted = 0;
foreach ($filelist as $filename) {
$filepath = $filename['fullname'];
if (!(substr_compare($filepath, 'webp', -strlen('webp')) === 0)) {
if (!empty($file) || !dol_is_file($filepathnoext.'.webp')) { // If file does not exists yet
if (image_format_supported($filepath) == 1) {
$filepathnoext = preg_replace("/\.[a-z0-9]+$/i", "", $filepath);
$result = dol_imageResizeOrCrop($filepath, 0, 0, 0, 0, 0, $filepathnoext.'.webp', 90);
if (!dol_is_file($result)) {
$error++;
setEventMessages($result, null, 'errors');
} else {
$nbconverted++;
}
}
}
}
if ($error) {
break;
}
}
if (!$error) {
if (!empty($file)) {
setEventMessages($langs->trans('SucessConvertChosenImgWebp'), null);
} else {
setEventMessages($langs->trans('SucessConvertImgWebp'), null);
}
}
$action = 'file_manager';
}
// List of directories
if (empty($action) || $action == 'editfile' || $action == 'file_manager' || preg_match('/refresh/i', $action) || $action == 'delete') {
$langs->load("ecm");
print '<table class="liste centpercent noborder">'."\n";
print '<!-- Title for manual directories -->'."\n";
print '<tr class="liste_titre">'."\n";
print '<th class="liste_titre left">';
print '<span style="padding-left: 5px; padding-right: 5px;">'.$langs->trans("ECMSections").'</span>';
print '</th></tr>';
$showonrightsize = '';
// Manual section
$htmltooltip = $langs->trans("ECMAreaDesc2a");
$htmltooltip .= '<br>'.$langs->trans("ECMAreaDesc2b");
if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_ECM_DISABLE_JS')) {
// Show the link to "Root"
if ($showroot) {
$paramsroot = array('file_manager' => 1);
if (!empty($websitekey)) {
$paramsroot['website'] = $websitekey;
}
$paramsroot['pageid'] = $pageid;
print '<tr class="oddeven nohover"><td><div style="padding-left: 5px; padding-right: 5px;"><a href="'.dolBuildUrl($_SERVER["PHP_SELF"], $paramsroot).'">';
if ($module == 'medias') {
print $langs->trans("RootOfMedias");
} else {
print $langs->trans("Root");
}
print '</a></div></td></tr>';
}
print '<tr class="oddeven nohover"><td>';
// Show filemanager tree (will be filled by a call of ajax /ecm/tpl/enablefiletreeajax.tpl.php, later, that executes ajaxdirtree.php)
print '<div id="filetree" class="ecmfiletree"></div>';
if ($action == 'deletefile') {
print $form->formconfirm('eeeee', $langs->trans('DeleteFile'), $langs->trans('ConfirmDeleteFile'), 'confirm_deletefile', '', '', 'deletefile');
}
print '</td></tr>';
} else {
// Show filtree when ajax is disabled (rare)
print '<tr><td style="padding-left: 20px">';
$_POST['modulepart'] = $module;
$_POST['openeddir'] = GETPOST('openeddir');
$_POST['dir'] = empty($_POST['dir']) ? '/' : GETPOST('dir');
// Show filemanager tree (will be filled by direct include of ajaxdirtree.php in mode noajax, this will return all dir - all levels - to show)
print '<div id="filetree" class="ecmfiletree">';
// Variables that may be defined:
// $_GET['modulepart'], $_GET['openeddir'], $_GET['sortfield'], $_GET['sortorder']
// $_POST['dir']
$mode = 'noajax';
if (empty($url)) {
$url = DOL_URL_ROOT.'/ecm/index.php';
}
include DOL_DOCUMENT_ROOT.'/core/ajax/ajaxdirtree.php';
print '</div>';
print '</td></tr>';
}
print "</table>";
}
// End left panel
?>
</div>
<div id="ecm-layout-center" class="inline-block">
<div class="pane-in ecm-in-layout-center">
<div id="ecmfileview" class="ecmfileview">
<?php
// Start right panel - List of content of a directory
$mode = 'noajax';
if (empty($url)) { // autoset $url but it is better to have it defined before (for example by ecm/index.php, ecm/index_medias.php, website/index.php)
if (!empty($module) && $module == 'medias' && !GETPOST('website')) {
$url = DOL_URL_ROOT.'/ecm/index_medias.php';
} elseif (GETPOSTISSET('website')) {
$url = DOL_URL_ROOT.'/website/index.php';
} else {
$url = DOL_URL_ROOT.'/ecm/index.php';
}
}
include DOL_DOCUMENT_ROOT.'/core/ajax/ajaxdirpreview.php'; // Show content of a directory on right side
// End right panel
?>
</div>
</div>
</div>
</div> <!-- End div id="containerlayout" -->
<?php
if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_ECM_DISABLE_JS')) { // Show filtree when ajax is enabled
//var_dump($modulepart);
// Variables that may be defined:
// $_GET['modulepart'], $_GET['openeddir'], $_GET['sortfield'], $_GET['sortorder']
// $_POST['dir']
// $_POST['section_dir'], $_POST['section_id'], $_POST['token'], $_POST['max_file_size'], $_POST['sendit']
if (GETPOST('section_dir', 'alpha')) {
$preopened = GETPOST('section_dir', 'alpha');
}
include DOL_DOCUMENT_ROOT.'/ecm/tpl/enablefiletreeajax.tpl.php';
}
?>
<!-- END PHP TEMPLATE core/tpl/filemanager.tpl.php -->