NEW: Add Shared links on links in attachements (#33459)

* NEW: add share option for URL link in attachement

* NEW: fix #33456

* NEW: shared link on links

* NEW: shared link on links

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
This commit is contained in:
HENRY Florian
2025-03-19 16:04:11 +01:00
committed by GitHub
parent 065ac4571f
commit 8f2ad4bf87
4 changed files with 111 additions and 45 deletions

View File

@@ -217,6 +217,14 @@ if ($action == 'confirm_deletefile' && $confirm == 'yes' && !empty($permissionto
$link->url = 'http://'.$link->url;
}
$link->label = GETPOST('label', 'alphanohtml');
$shareenabled = GETPOST('shareenabled', 'alpha');
if ($shareenabled) {
require_once DOL_DOCUMENT_ROOT.'/core/lib/security2.lib.php';
$link->share = getRandomPassword(true);
} else {
$link->share = '';
}
$res = $link->update($user);
if (!$res) {
setEventMessages($langs->trans("ErrorFailedToUpdateLink", $link->label), null, 'mesgs');

View File

@@ -2222,7 +2222,7 @@ class FormFile
$sortfield = '';
}
$res = $link->fetchAll($links, $object->element, $object->id, $sortfield, $sortorder);
$param .= (isset($object->id) ? '&id='.$object->id : '');
$param .= (isset($object->id) && !preg_match('/&id='.$object->id.'/i', $param) ? '&id='.$object->id : '');
$permissiontoedit = $permissiontodelete;
@@ -2289,6 +2289,7 @@ class FormFile
'',
'center '
);
// Shared or not - Hash of file
print_liste_field_titre('', '', '');
print '</tr>';
$nboflinks = count($links);
@@ -2298,7 +2299,7 @@ class FormFile
foreach ($links as $link) {
print '<tr class="oddeven">';
//edit mode
if ($action == 'update' && $selected === (int) $link->id && $permissiontoedit) {
if ($action == 'update' && (int) $selected === (int) $link->id && $permissiontoedit) {
print '<td>';
print '<input type="hidden" name="id" value="'.$object->id.'">';
print '<input type="hidden" name="linkid" value="'.$link->id.'">';
@@ -2309,7 +2310,10 @@ class FormFile
print $langs->trans('Label').': <input type="text" name="label" value="'.dol_escape_htmltag($link->label).'">';
print '</td>';
print '<td class="center">'.dol_print_date(dol_now(), "dayhour", "tzuser").'</td>';
print '<td class="right"></td>';
print '<td class="right">';
print '<label for="idshareenabled'.$key.'">'.$langs->trans("LinkSharedViaALink").'</label> ';
print '<input class="inline-block" type="checkbox" id="idshareenabled'.$key.'" name="shareenabled"'.($link->share ? ' checked="checked"' : '').' /> ';
print '</td>';
print '<td class="right">';
print '<input type="submit" class="button button-save" name="save" value="'.dol_escape_htmltag($langs->trans("Save")).'">';
print '<input type="submit" class="button button-cancel" name="cancel" value="'.dol_escape_htmltag($langs->trans("Cancel")).'">';
@@ -2323,7 +2327,17 @@ class FormFile
print '</td>'."\n";
print '<td class="right"></td>';
print '<td class="center">'.dol_print_date($link->datea, "dayhour", "tzuser").'</td>';
print '<td class="center"></td>';
print '<td class="center">';
if ($link->share) {
global $dolibarr_main_url_root;
$urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($dolibarr_main_url_root));
$urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file
$fulllink = $urlwithroot.'/document.php?type=link&hashp=' . $link->share;
print '<a href="'.$fulllink.'" target="_blank" rel="noopener">'.img_picto($langs->trans("FileSharedViaALink"), 'globe').'</a> ';
print '<input type="text" class="centpercentminusx minwidth200imp nopadding small" id="downloadlink'.$link->id.'" name="downloadexternallink" title="'.dol_escape_htmltag($langs->trans("LinkSharedViaALink")).'" value="'.dol_escape_htmltag($fulllink).'">';
}
print '</td>';
print '<td class="right">';
print '<a href="'.$_SERVER['PHP_SELF'].'?action=update&linkid='.$link->id.$param.'&token='.newToken().'" class="editfilelink editfielda reposition" >'.img_edit().'</a>'; // id= is included into $param
if ($permissiontodelete) {

View File

@@ -70,6 +70,15 @@ class Link extends CommonObject
*/
public $objectid;
/**
* @var string share hash
*/
public $share;
/**
* @var string share pass hash
*/
public $share_pass;
/**
* Constructor
@@ -113,12 +122,14 @@ class Link extends CommonObject
$this->db->begin();
$sql = "INSERT INTO ".$this->db->prefix()."links (entity, datea, url, label, objecttype, objectid)";
$sql = "INSERT INTO ".$this->db->prefix()."links (entity, datea, url, label, objecttype, objectid, share,share_pass)";
$sql .= " VALUES (".$conf->entity.", '".$this->db->idate($this->datea)."'";
$sql .= ", '".$this->db->escape($this->url)."'";
$sql .= ", '".$this->db->escape($this->label)."'";
$sql .= ", '".$this->db->escape($this->objecttype)."'";
$sql .= ", ".((int) $this->objectid).")";
$sql .= ", ".((int) $this->objectid);
$sql .= ', '.(!empty($this->share) ? "'".$this->db->escape($this->share)."'" : "null");
$sql .= ', '.(!empty($this->share_pass) ? "'".$this->db->escape($this->share_pass)."'" : "null").")";
dol_syslog(get_class($this)."::create", LOG_DEBUG);
$result = $this->db->query($sql);
@@ -198,6 +209,8 @@ class Link extends CommonObject
$sql .= ", label = '".$this->db->escape($this->label)."'";
$sql .= ", objecttype = '".$this->db->escape($this->objecttype)."'";
$sql .= ", objectid = ".$this->objectid;
$sql .= ', share = '.(!empty($this->share) ? "'".$this->db->escape($this->share)."'" : "null");
$sql .= ', share_pass = '.(!empty($this->share_pass) ? "'".$this->db->escape($this->share_pass)."'" : "null");
$sql .= " WHERE rowid = ".((int) $this->id);
dol_syslog(get_class($this)."::update sql = ".$sql);
@@ -249,7 +262,7 @@ class Link extends CommonObject
{
global $conf;
$sql = "SELECT rowid, entity, datea, url, label, objecttype, objectid FROM ".$this->db->prefix()."links";
$sql = "SELECT rowid, entity, datea, url, label, objecttype, objectid, share,share_pass FROM ".$this->db->prefix()."links";
$sql .= " WHERE objecttype = '".$this->db->escape($objecttype)."' AND objectid = ".((int) $objectid);
if ($conf->entity != 0) {
$sql .= " AND entity = ".((int) $conf->entity);
@@ -276,6 +289,8 @@ class Link extends CommonObject
$link->label = $obj->label;
$link->objecttype = $obj->objecttype;
$link->objectid = $obj->objectid;
$link->share = $obj->share;
$link->share_pass = $obj->share_pass;
$links[] = $link;
}
return 1;
@@ -319,9 +334,10 @@ class Link extends CommonObject
* Loads a link from database
*
* @param int $rowid Id of link to load
* @param string $hashforshare Hash of file sharing, or 'shared'
* @return int 1 if ok, 0 if no record found, -1 if error
**/
public function fetch($rowid = null)
public function fetch($rowid = null, $hashforshare = '')
{
global $conf;
@@ -329,10 +345,21 @@ class Link extends CommonObject
$rowid = $this->id;
}
$sql = "SELECT rowid, entity, datea, url, label, objecttype, objectid FROM ".$this->db->prefix()."links";
$sql .= " WHERE rowid = ".((int) $rowid);
$sqlwhere=[];
$sql = "SELECT rowid, entity, datea, url, label, objecttype, objectid, share, share_pass FROM ".$this->db->prefix()."links";
if (!empty((int) $rowid)) {
$sqlwhere[] = " rowid = ".((int) $rowid);
}
if (!empty($hashforshare)) {
$sqlwhere[] = " share = '".$this->db->escape($hashforshare)."'";
}
if ($conf->entity != 0) {
$sql .= " AND entity = ".$conf->entity;
$sqlwhere[] = " entity = ".$conf->entity;
}
if (count($sqlwhere)>0) {
$sql .=' WHERE '.implode(' AND ', $sqlwhere);
}
dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
@@ -348,6 +375,8 @@ class Link extends CommonObject
$this->label = $obj->label;
$this->objecttype = $obj->objecttype;
$this->objectid = $obj->objectid;
$this->share = $obj->share;
$this->share_pass = $obj->share_pass;
return 1;
} else {
return 0;

View File

@@ -177,43 +177,58 @@ if (in_array($modulepart, array('facture_paiement', 'unpaid'))) {
// If we have a hash public (hashp), we guess the original_file.
$ecmfile = '';
if (!empty($hashp)) {
include_once DOL_DOCUMENT_ROOT.'/ecm/class/ecmfiles.class.php';
$ecmfile = new EcmFiles($db);
$result = $ecmfile->fetch(0, '', '', '', $hashp);
if ($result > 0) {
$tmp = explode('/', $ecmfile->filepath, 2); // $ecmfile->filepath is relative to document directory
// filepath can be 'users/X' or 'X/propale/PR11111'
if (is_numeric($tmp[0])) { // If first tmp is numeric, it is subdir of company for multicompany, we take next part.
$tmp = explode('/', $tmp[1], 2);
}
$moduleparttocheck = $tmp[0]; // moduleparttocheck is first part of path
if ($modulepart) { // Not required, so often not defined, for link using public hashp parameter.
if ($moduleparttocheck == $modulepart) {
// We remove first level of directory
$original_file = (($tmp[1] ? $tmp[1].'/' : '').$ecmfile->filename); // this is relative to module dir
//var_dump($original_file); exit;
} else {
httponly_accessforbidden('Bad link. File is from another module part.', 403);
}
if (GETPOST('type', 'alpha')=='link') {
require_once DOL_DOCUMENT_ROOT.'/core/class/link.class.php';
$link = new Link($db);
$result = $link->fetch(0, $hashp);
if ($result > 0 && !empty($link->url)) {
header('Location: '.$link->url);
exit;
} else {
$modulepart = $moduleparttocheck;
$original_file = (($tmp[1] ? $tmp[1].'/' : '').$ecmfile->filename); // this is relative to module dir
}
$entity = $ecmfile->entity;
if (isModEnabled('multicompany') && !empty($ecmfile->src_object_type) && $ecmfile->src_object_id > 0) {
$object = fetchObjectByElement($ecmfile->src_object_id, $ecmfile->src_object_type);
if (is_object($object) && $object->id > 0) {
$entity = $object->entity;
}
}
if ($entity != $conf->entity) {
$conf->entity = $entity;
$conf->setValues($db);
$langs->load("errors");
httponly_accessforbidden($langs->trans("ErrorLinkNotFoundWithSharedLink"), 403, 1);
}
} else {
$langs->load("errors");
httponly_accessforbidden($langs->trans("ErrorFileNotFoundWithSharedLink"), 403, 1);
include_once DOL_DOCUMENT_ROOT . '/ecm/class/ecmfiles.class.php';
$ecmfile = new EcmFiles($db);
$result = $ecmfile->fetch(0, '', '', '', $hashp);
if ($result > 0) {
$tmp = explode('/', $ecmfile->filepath, 2); // $ecmfile->filepath is relative to document directory
// filepath can be 'users/X' or 'X/propale/PR11111'
if (is_numeric($tmp[0])) { // If first tmp is numeric, it is subdir of company for multicompany, we take next part.
$tmp = explode('/', $tmp[1], 2);
}
$moduleparttocheck = $tmp[0]; // moduleparttocheck is first part of path
if ($modulepart) { // Not required, so often not defined, for link using public hashp parameter.
if ($moduleparttocheck == $modulepart) {
// We remove first level of directory
$original_file = (($tmp[1] ? $tmp[1] . '/' : '') . $ecmfile->filename); // this is relative to module dir
//var_dump($original_file); exit;
} else {
httponly_accessforbidden('Bad link. File is from another module part.', 403);
}
} else {
$modulepart = $moduleparttocheck;
$original_file = (($tmp[1] ? $tmp[1] . '/' : '') . $ecmfile->filename); // this is relative to module dir
}
$entity = $ecmfile->entity;
if (isModEnabled('multicompany') && !empty($ecmfile->src_object_type) && $ecmfile->src_object_id > 0) {
$object = fetchObjectByElement($ecmfile->src_object_id, $ecmfile->src_object_type);
if (is_object($object) && $object->id > 0) {
$entity = $object->entity;
}
}
if ($entity != $conf->entity) {
$conf->entity = $entity;
$conf->setValues($db);
}
} else {
$langs->load("errors");
httponly_accessforbidden($langs->trans("ErrorFileNotFoundWithSharedLink"), 403, 1);
}
}
}