DocumentStorageService.php 3.8 KB

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