file_deleter = $file_deleter ?? new Audio_File_Deleter(); $this->metadata_manager = $metadata_manager ?? new Audio_File_Metadata(); } /** * Clean up audio file and metadata for a post. * * @param int $post_id Post ID. * @return array Cleanup result. */ public function cleanup_post_audio( int $post_id ): array { accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Cleaning up audio for post ID: ' . $post_id, true ); try { $results = array( 'post_id' => $post_id, 'file_deleted' => false, 'metadata_deleted' => false, 'errors' => array(), ); // Get metadata first. $metadata = $this->metadata_manager->get_metadata( $post_id ); // Older installs/imports may have only URL metadata; private URLs are not filesystem paths. if ( class_exists( '\\Accessibility_Audio_TTS\\Core\\Audio_Access' ) ) { $file = \Accessibility_Audio_TTS\Core\Audio_Access::resolve_audio_file( $post_id ); if ( $file && ! \Accessibility_Audio_TTS\Core\Audio_Access::delete_audio_file( $post_id ) ) { throw new \RuntimeException( 'Cannot delete the audio file.' ); } $this->metadata_manager->delete_metadata( $post_id ); delete_post_meta( $post_id, 'accessibility_audio_tts_generated' ); delete_post_meta( $post_id, 'accessibility_audio_tts_generated_date' ); return array_merge( $results, array( 'success' => true, 'file_deleted' => true, 'metadata_deleted' => true ) ); } if ( null === $metadata ) { accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] No metadata found for post ID: ' . $post_id, true ); return array_merge( $results, array( 'success' => true, 'message' => __( 'No audio file to clean up', 'accessibility-audio-tts-text-to-speech-for-articles' ), ) ); } // Delete physical file. if ( isset( $metadata['url'] ) ) { $file_delete_result = $this->file_deleter->delete_audio_file( $metadata['url'] ); $results['file_deleted'] = $file_delete_result; if ( ! $file_delete_result ) { $results['errors'][] = __( 'Failed to delete audio file', 'accessibility-audio-tts-text-to-speech-for-articles' ); } } // Delete metadata. $metadata_delete_result = $this->metadata_manager->delete_metadata( $post_id ); $results['metadata_deleted'] = $metadata_delete_result; if ( ! $metadata_delete_result ) { $results['errors'][] = __( 'Failed to delete audio metadata', 'accessibility-audio-tts-text-to-speech-for-articles' ); } $success = $results['file_deleted'] && $results['metadata_deleted']; accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Cleanup completed for post ID: ' . $post_id . '. Success: ' . ( $success ? 'yes' : 'no' ), true ); return array_merge( $results, array( 'success' => $success, 'message' => $success ? __( 'Audio file cleaned up successfully', 'accessibility-audio-tts-text-to-speech-for-articles' ) : __( 'Partial cleanup completed with errors', 'accessibility-audio-tts-text-to-speech-for-articles' ), ) ); } catch ( \Throwable $e ) { accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Error during cleanup: ' . $e->getMessage(), true ); return array( 'success' => false, 'post_id' => $post_id, 'error' => __( 'Error during cleanup: ', 'accessibility-audio-tts-text-to-speech-for-articles' ) . $e->getMessage(), 'file_deleted' => false, 'metadata_deleted' => false, ); } } /** * Clean up multiple posts' audio files. * * @param array $post_ids Array of post IDs. * @return array Cleanup results. */ public function cleanup_multiple_posts( array $post_ids ): array { accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Cleaning up audio for ' . count( $post_ids ) . ' posts', true ); $results = array( 'total_posts' => count( $post_ids ), 'successful_cleanups' => 0, 'failed_cleanups' => 0, 'details' => array(), ); foreach ( $post_ids as $post_id ) { $cleanup_result = $this->cleanup_post_audio( $post_id ); $results['details'][ $post_id ] = $cleanup_result; if ( $cleanup_result['success'] ) { ++$results['successful_cleanups']; } else { ++$results['failed_cleanups']; } } accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Bulk cleanup completed. Success: ' . $results['successful_cleanups'] . '/' . $results['total_posts'], true ); return $results; } /** * Clean up orphaned files (files without metadata). * * @param string $upload_path Upload directory path. * @return array Cleanup result. */ public function cleanup_orphaned_files( string $upload_path ): array { accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Cleaning up orphaned files in: ' . $upload_path, true ); try { if ( ! is_dir( $upload_path ) ) { return array( 'success' => false, 'error' => __( 'Upload directory does not exist', 'accessibility-audio-tts-text-to-speech-for-articles' ), ); } // Get all audio files. $audio_files = glob( $upload_path . '/*.mp3' ); $orphaned_files = array(); $posts_with_audio = $this->metadata_manager->get_posts_with_audio(); // Get all valid file paths from metadata. $valid_paths = array(); foreach ( $posts_with_audio as $post_id ) { $path = $this->metadata_manager->get_audio_path( $post_id ); if ( $path ) { $valid_paths[] = $path; } } // Find orphaned files. foreach ( $audio_files as $file_path ) { if ( ! in_array( $file_path, $valid_paths, true ) ) { $orphaned_files[] = $file_path; } } // Delete orphaned files. $deleted_count = 0; $errors = array(); foreach ( $orphaned_files as $orphaned_file ) { if ( $this->file_deleter->delete_file_by_path( $orphaned_file ) ) { ++$deleted_count; accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Deleted orphaned file: ' . $orphaned_file, true ); } else { $errors[] = basename( $orphaned_file ); accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Failed to delete orphaned file: ' . $orphaned_file, true ); } } return array( 'success' => true, 'total_orphaned_files' => count( $orphaned_files ), 'deleted_files' => $deleted_count, 'failed_deletions' => count( $errors ), 'errors' => $errors, /* translators: %d: number of orphaned files deleted */ 'message' => sprintf( __( 'Cleaned up %d orphaned files', 'accessibility-audio-tts-text-to-speech-for-articles' ), $deleted_count ), ); } catch ( \Throwable $e ) { accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Error cleaning orphaned files: ' . $e->getMessage(), true ); return array( 'success' => false, 'error' => __( 'Error cleaning orphaned files: ', 'accessibility-audio-tts-text-to-speech-for-articles' ) . $e->getMessage(), ); } } /** * Clean up temporary files (chunk files, etc.) * * @param string $upload_path Upload directory path. * @param int $max_age_hours Maximum age in hours for temporary files. * @return array Cleanup result. */ public function cleanup_temporary_files( string $upload_path, int $max_age_hours = 24 ): array { accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Cleaning up temporary files older than ' . $max_age_hours . ' hours', true ); try { if ( ! is_dir( $upload_path ) ) { return array( 'success' => false, 'error' => __( 'Upload directory does not exist', 'accessibility-audio-tts-text-to-speech-for-articles' ), ); } // Find temporary files (chunk files, etc.). $temp_patterns = array( $upload_path . '/*_chunk_*.mp3', $upload_path . '/temp_*.mp3', $upload_path . '/*_temp.mp3', ); $temp_files = array(); foreach ( $temp_patterns as $pattern ) { $temp_files = array_merge( $temp_files, glob( $pattern ) ); } $cutoff_time = time() - ( $max_age_hours * 3600 ); $deleted_count = 0; $errors = array(); foreach ( $temp_files as $temp_file ) { if ( filemtime( $temp_file ) < $cutoff_time ) { if ( $this->file_deleter->delete_file_by_path( $temp_file ) ) { ++$deleted_count; accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Deleted old temporary file: ' . $temp_file, true ); } else { $errors[] = basename( $temp_file ); accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Failed to delete temporary file: ' . $temp_file, true ); } } } return array( 'success' => true, 'total_temp_files_found' => count( $temp_files ), 'deleted_files' => $deleted_count, 'failed_deletions' => count( $errors ), 'errors' => $errors, /* translators: %d: number of temporary files deleted */ 'message' => sprintf( __( 'Cleaned up %d temporary files', 'accessibility-audio-tts-text-to-speech-for-articles' ), $deleted_count ), ); } catch ( \Throwable $e ) { accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Error cleaning temporary files: ' . $e->getMessage(), true ); return array( 'success' => false, 'error' => __( 'Error cleaning temporary files: ', 'accessibility-audio-tts-text-to-speech-for-articles' ) . $e->getMessage(), ); } } /** * Perform comprehensive cleanup. * * @param string $upload_path Upload directory path. * @return array Comprehensive cleanup result. */ public function comprehensive_cleanup( string $upload_path ): array { accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Starting comprehensive cleanup', true ); $results = array( 'orphaned_cleanup' => $this->cleanup_orphaned_files( $upload_path ), 'temporary_cleanup' => $this->cleanup_temporary_files( $upload_path ), 'total_files_deleted' => 0, 'total_errors' => 0, ); // Calculate totals. $results['total_files_deleted'] = ( $results['orphaned_cleanup']['deleted_files'] ?? 0 ) + ( $results['temporary_cleanup']['deleted_files'] ?? 0 ); $results['total_errors'] = ( $results['orphaned_cleanup']['failed_deletions'] ?? 0 ) + ( $results['temporary_cleanup']['failed_deletions'] ?? 0 ); $results['success'] = $results['orphaned_cleanup']['success'] && $results['temporary_cleanup']['success']; $results['message'] = sprintf( /* translators: 1: number of files deleted, 2: number of errors */ __( 'Comprehensive cleanup completed. Deleted %1$d files with %2$d errors.', 'accessibility-audio-tts-text-to-speech-for-articles' ), $results['total_files_deleted'], $results['total_errors'] ); accessibility_audio_tts_debug_log( '[Audio_File_Cleaner] Comprehensive cleanup completed. Files deleted: ' . $results['total_files_deleted'], true ); return $results; } }