Dolibarr/dolibarr · error
Bad link. File is from another module part.
Error message
Bad link. File is from another module part.
What it means
When a file is fetched via shared hash, document.php derives a moduleparttocheck from the stored ECM record and compares it to the modulepart supplied in the URL. If a modulepart is given and it does not match the record's module, access is forbidden with HTTP 403 — preventing a hash for one module's file being combined with another module's path.
Solutions
- Use the URL exactly as generated by Dolibarr — do not alter modulepart
- Omit modulepart for hashp-based links (it is optional for shared links); Dolibarr will infer it from the record
- Regenerate the link from the correct module's document list
- Check for module renames/custom modulepart mismatches between stored ecm record and URL
Example fix
// before document.php?modulepart=facture&hashp=<hash of an ecm file owned by 'ecm'> // after document.php?hashp=<hash>
Defensive patterns
Strategy: validation
Validate before calling
// ensure URL modulepart matches the ecm record before building link
if (!empty($modulepart) && $modulepart !== $ecmfile->src_object_type_module) {
throw new Exception('modulepart mismatch with file owner module');
} Prevention
- Never hand-edit modulepart in share URLs
- For hashp links, omit modulepart and let Dolibarr infer it
- Keep modulepart values consistent across module renames
- Test share links after enabling/renaming external modules
When it happens
Trigger: document.php called with both hashp (or ecmfile lookup) and a modulepart that differs from the module stored on the ECM file record, e.g. modulepart=invoice for a file stored under 'expensereport' — the `$moduleparttocheck != $modulepart` branch fires.
Common situations: Manually edited share URLs where modulepart was changed; links generated for one module then reused for files of another; module renamed between link creation and click (e.g. fckeditor→medias mismatches).
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Access refused to by SQL or Script injection protection in…
- Access to a page that needs a token (constant…
- Access to this page this way (POST method or GET with a…
- If you access your server behind a proxy using url…
- ErrorLoginMustBePostMethod
AI-assisted analysis of Dolibarr/dolibarr@598aa4bdad (2026-09-14).
Data as JSON: /api/errors/c77551b973b69ac5.
Report an issue: GitHub.
Appendix: source
Thrown at htdocs/document.php:224
} else {
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);
// Multicompany: Here we are switching entity and later we will check the requested object is in this entity but may be that user is not allowed to log/see entityView on GitHub (pinned to 598aa4bdad)