Jake Vanderwerf
2026-02-14 27fb820ae9081fb56957cf75e79eccd8a99edd52
inc/managers/UploadManager.php
@@ -505,7 +505,7 @@
      return apply_filters(
         'jvb_upload_filename',
         "{$username}-{$base_name}-{$timestamp}",
         "{$base_name}-{$timestamp}",
         $context
      );
   }
@@ -751,4 +751,159 @@
      apply_filters('jvb_extract_video_thumbnail', null, $attachment_id, $video_path, $time);
   }
   /**
    * Phase 1: Validate and process file on disk (no DB writes)
    * Safe to run outside transactions — only does file I/O
    *
    * @return array Prepared file info for registerFile()
    */
   public function prepareFile(string $temp_path, array $context): array
   {
      if (!file_exists($temp_path)) {
         throw new Exception('Temporary file not found: ' . $temp_path);
      }
      $mime_type = mime_content_type($temp_path);
      if (!$this->validateMimeType($temp_path, $mime_type)) {
         throw new Exception('Invalid or potentially dangerous file type');
      }
      $file_type = $this->allowedTypes[$mime_type] ?? 'unknown';
      if ($file_type === 'unknown') {
         throw new Exception('Unsupported file type: ' . $mime_type);
      }
      $config  = array_merge($this->defaultConfig, $context['config'] ?? [], $context);
      $post_id = $context['post_id'] ?? 0;
      if (!empty($config['allowed_types']) && !in_array($mime_type, $config['allowed_types'])) {
         throw new Exception('File type not allowed: ' . $mime_type);
      }
      $this->validateFileSize($temp_path, $file_type, $config);
      $directory = $this->generateDirectory(
         $file_type,
         $config['directory_pattern'] ?? '{user_id}/{content}/{file_type}',
         $context
      );
      $this->ensureDirectories($directory);
      $original_name = $context['original_name'] ?? basename($temp_path);
      $filename      = $this->generateFilename($original_name, $context);
      $original_path = $this->storeOriginalFromTemp($temp_path, $directory, $filename);
      // File-type-specific I/O (conversion, copying) — no DB
      $final_path = match ($file_type) {
         'image'    => $this->prepareImage($original_path, $directory, $filename, $config),
         'video'    => $this->prepareMedia($original_path, $directory, $filename),
         'document' => $this->prepareMedia($original_path, $directory, $filename),
         default    => throw new Exception('Unknown file type'),
      };
      return [
         'final_path' => $final_path,
         'file_type'  => $file_type,
         'filename'   => $filename,
         'post_id'    => $post_id,
         'config'     => $config,
         'context'    => $context,
      ];
   }
   /**
    * Phase 2: Register processed file in WordPress (DB writes only)
    * Quick operation — individual WP functions handle their own queries
    *
    * @param array $prepared Output from prepareFile()
    * @return array Standard upload result
    */
   public function registerFile(array $prepared): array
   {
      $attachment_id = $this->createAttachment(
         $prepared['final_path'],
         $prepared['filename'],
         $prepared['context'],
         $prepared['post_id']
      );
      // Type-specific post-registration
      if ($prepared['file_type'] === 'image') {
         $alt_text = apply_filters('jvb_upload_alt_text', '', $prepared['context']);
         if ($alt_text !== '') {
            update_post_meta($attachment_id, '_wp_attachment_image_alt', $alt_text);
         }
      }
      if ($prepared['file_type'] === 'video' && ($prepared['config']['extract_video_thumbnail'] ?? false)) {
         $this->extractVideoThumbnail(
            $attachment_id,
            $prepared['final_path'],
            $prepared['config']['video_thumbnail_time'] ?? 0
         );
      }
      return [
         'success'       => true,
         'attachment_id' => $attachment_id,
         'url'           => wp_get_attachment_url($attachment_id),
         'file'          => $prepared['final_path'],
         'type'          => $prepared['file_type'],
      ];
   }
   /**
    * Image file I/O: convert or copy to final location
    */
   protected function prepareImage(string $original_path, string $directory, string $filename, array $config): string
   {
      $full_dir      = "{$this->upload_dir}/{$directory}";
      $original_mime = mime_content_type($original_path);
      $should_convert = false;
      $target_format  = false;
      if ($config['convert'] && $config['convert'] !== false) {
         $target_format = strtolower($config['convert']);
         if (!isset($this->supportedFormats[$target_format])) {
            throw new Exception("Unsupported conversion format: {$target_format}");
         }
         $should_convert = ($original_mime !== $this->supportedFormats[$target_format]);
      }
      if ($should_convert) {
         $final_path = "{$full_dir}/{$filename}.{$target_format}";
         try {
            $this->convertImage($original_path, $final_path, $target_format, $config['quality'], $config['use_imagick']);
         } catch (Exception $e) {
            JVB()->error()->log('Image conversion failed: ' . $e->getMessage());
            $ext = pathinfo($original_path, PATHINFO_EXTENSION);
            $final_path = "{$full_dir}/{$filename}.{$ext}";
            copy($original_path, $final_path);
         }
      } else {
         $ext = pathinfo($original_path, PATHINFO_EXTENSION);
         $final_path = "{$full_dir}/{$filename}.{$ext}";
         copy($original_path, $final_path);
      }
      return $final_path;
   }
   /**
    * Video/document file I/O: copy to final location
    */
   protected function prepareMedia(string $original_path, string $directory, string $filename): string
   {
      $full_dir   = "{$this->upload_dir}/{$directory}";
      $ext        = pathinfo($original_path, PATHINFO_EXTENSION);
      $final_path = "{$full_dir}/{$filename}.{$ext}";
      copy($original_path, $final_path);
      return $final_path;
   }
}