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

  1. Use the URL exactly as generated by Dolibarr — do not alter modulepart
  2. Omit modulepart for hashp-based links (it is optional for shared links); Dolibarr will infer it from the record
  3. Regenerate the link from the correct module's document list
  4. 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

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


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 entity

View on GitHub (pinned to 598aa4bdad)