DocumentStorageService.php 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. <?php
  2. namespace Services;
  3. class DocumentStorageService
  4. {
  5. private string $rootDir;
  6. public function __construct(?string $rootDir = null)
  7. {
  8. $this->rootDir = $rootDir ?? ($_ENV['DOCUMENTS_ROOT'] ?? '/data/documents');
  9. }
  10. public function sanitizeDocumentType(string $documentType): string
  11. {
  12. $documentType = trim($documentType);
  13. $documentType = preg_replace('/[^a-zA-Z0-9._-]+/', '_', $documentType);
  14. $documentType = trim($documentType, '._-');
  15. if ($documentType === '') {
  16. throw new \RuntimeException('Invalid document_type');
  17. }
  18. return $documentType;
  19. }
  20. public function ensureDirectory(int $companyId, int $userId, string $documentType): string
  21. {
  22. $documentType = $this->sanitizeDocumentType($documentType);
  23. $dir = rtrim($this->rootDir, '/');
  24. $dir .= '/' . $companyId;
  25. $dir .= '/' . $userId;
  26. $dir .= '/' . $documentType;
  27. if (!is_dir($dir)) {
  28. if (!@mkdir($dir, 0775, true) && !is_dir($dir)) {
  29. $lastError = error_get_last();
  30. $root = rtrim($this->rootDir, '/');
  31. $rootParent = dirname($root);
  32. $debug = [
  33. 'root_dir' => $root,
  34. 'root_dir_exists' => is_dir($root),
  35. 'root_dir_writable' => is_writable($root),
  36. 'root_parent' => $rootParent,
  37. 'root_parent_exists' => is_dir($rootParent),
  38. 'root_parent_writable' => is_writable($rootParent),
  39. 'target_dir' => $dir,
  40. 'target_parent' => dirname($dir),
  41. 'target_parent_exists' => is_dir(dirname($dir)),
  42. 'target_parent_writable' => is_writable(dirname($dir)),
  43. 'php_user' => get_current_user(),
  44. 'euid' => function_exists('posix_geteuid') ? posix_geteuid() : null,
  45. 'last_error' => $lastError,
  46. ];
  47. throw new \RuntimeException('Failed to create documents directory: ' . json_encode($debug, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
  48. }
  49. }
  50. return $dir;
  51. }
  52. public function buildStoredFilename(string $originalFilename, ?string $contentType = null): string
  53. {
  54. $ext = pathinfo($originalFilename, PATHINFO_EXTENSION);
  55. $ext = $ext ? strtolower($ext) : '';
  56. if ($ext === '' && $contentType) {
  57. if (stripos($contentType, 'pdf') !== false) {
  58. $ext = 'pdf';
  59. }
  60. }
  61. $name = bin2hex(random_bytes(16));
  62. return $ext !== '' ? ($name . '.' . $ext) : $name;
  63. }
  64. public function writeFile(string $dir, string $filename, string $content): string
  65. {
  66. $dir = rtrim($dir, '/');
  67. $path = $dir . '/' . $filename;
  68. $bytes = @file_put_contents($path, $content, LOCK_EX);
  69. if ($bytes === false) {
  70. $lastError = error_get_last();
  71. $debug = [
  72. 'path' => $path,
  73. 'dir_exists' => is_dir($dir),
  74. 'dir_writable' => is_writable($dir),
  75. 'last_error' => $lastError,
  76. ];
  77. throw new \RuntimeException('Failed to write file: ' . json_encode($debug, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
  78. }
  79. return $path;
  80. }
  81. }