setup_paths(); } /** * Get upload path. * * @return string Absolute path. */ public function get_upload_path() { return $this->upload_path; } /** * Get upload URL. * * @return string URL. */ public function get_upload_url(): string { // Lazily initialize if not yet set to guarantee string return type. if ( empty( $this->upload_url ) ) { $this->setup_paths(); } return (string) $this->upload_url; } /** * Ensure directory exists and is writable. * * @return bool True on success. */ public function ensure_directory_exists() { if ( ! file_exists( $this->upload_path ) ) { accessibility_audio_tts_debug_log( 'Creating directory: ' . $this->upload_path ); $dir_created = wp_mkdir_p( $this->upload_path ); if ( ! $dir_created ) { accessibility_audio_tts_debug_log( 'Failed to create directory: ' . $this->upload_path ); return false; } // Add .htaccess file for security. $this->create_htaccess_file(); } // Ensure writable using centralized helper when available; otherwise fallback to WordPress core. if ( class_exists( '\\Accessibility_Audio_TTS\\API\\File_Validation_Helper' ) ) { if ( ! File_Validation_Helper::ensure_directory_writable( $this->upload_path ) ) { return false; } } elseif ( function_exists( 'wp_is_writable' ) ) { if ( ! wp_is_writable( $this->upload_path ) ) { return false; } } else { global $wp_filesystem; if ( ! $wp_filesystem ) { require_once ABSPATH . 'wp-admin/includes/file.php'; WP_Filesystem(); } if ( $wp_filesystem ) { $test_file = rtrim( $this->upload_path, '/\\' ) . '/.aat-writable-check'; $wrote = $wp_filesystem->put_contents( $test_file, 'ok' ); if ( ! $wrote ) { return false; } $wp_filesystem->delete( $test_file ); } else { return false; } } return true; } /** * Check and fix directory permissions. * * Override parent method to handle year/month subdirectories. * * @return bool True on success. */ public function check_and_fix_permissions(): bool { try { accessibility_audio_tts_debug_log( 'Running permission check and fix.' ); // First use parent method to check base directory. if ( ! parent::check_and_fix_permissions() ) { return false; } // Then check year/month subdirectories. $directories = $this->get_required_directories(); foreach ( $directories as $dir ) { if ( ! $this->ensure_single_directory( $dir ) ) { return false; } } accessibility_audio_tts_debug_log( 'All directories exist and are writable.' ); return true; } catch ( \Exception $e ) { accessibility_audio_tts_debug_log( 'Exception in check_and_fix_permissions: ' . $e->getMessage() ); return false; } } /** * Migrate legacy wcag-audio directory to accessibility-audio-tts. * * Renames the old upload directory and updates audio URLs stored in post meta. * Safe to call multiple times — skips if already migrated. * * @return bool True if migration was performed or not needed, false on failure. */ public function maybe_migrate_legacy_directory() { global $wp_filesystem; if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } WP_Filesystem(); $upload_dir = wp_upload_dir(); $old_dir = $upload_dir['basedir'] . '/wcag-audio'; $new_dir = $upload_dir['basedir'] . '/accessibility-audio-tts'; // Already migrated or nothing to migrate. if ( ! is_dir( $old_dir ) ) { return true; } // If new dir already exists (partial migration), skip rename. if ( ! is_dir( $new_dir ) ) { $renamed = $wp_filesystem->move( $old_dir, $new_dir ); if ( ! $renamed ) { accessibility_audio_tts_debug_log( 'Failed to rename ' . $old_dir . ' to ' . $new_dir ); return false; } } // Update audio URLs in post meta. $old_url = $upload_dir['baseurl'] . '/wcag-audio'; $new_url = $upload_dir['baseurl'] . '/accessibility-audio-tts'; global $wpdb; $meta_keys = array( 'accessibility_audio_tts_url', 'accessibility_audio_tts_file', ); foreach ( $meta_keys as $meta_key ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-time migration. $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, %s, %s) WHERE meta_key = %s AND meta_value LIKE %s", $old_url, $new_url, $meta_key, $wpdb->esc_like( $old_url ) . '%' ) ); } // Migrate legacy debug log. $old_log = $upload_dir['basedir'] . '/wcag-audio-debug.log'; $new_log = $new_dir . '/debug.log'; if ( file_exists( $old_log ) ) { $wp_filesystem->move( $old_log, $new_log ); } // Migrate legacy compressed assets. $old_compressed = $upload_dir['basedir'] . '/wcag-audio-compressed'; $new_compressed = $new_dir . '/compressed'; if ( is_dir( $old_compressed ) && ! is_dir( $new_compressed ) ) { $wp_filesystem->move( $old_compressed, $new_compressed ); } accessibility_audio_tts_debug_log( 'Migration completed: wcag-audio → accessibility-audio-tts' ); return true; } /** * Configure directory paths. * * @return void */ private function setup_paths() { $this->relative_path = 'uploads/accessibility-audio-tts/' . gmdate( 'Y/m' ); // Use parent methods to avoid calling this class' accessors before properties are initialized. $this->upload_path = parent::get_upload_directory() . '/' . gmdate( 'Y/m' ); $this->upload_url = parent::get_upload_url() . '/' . gmdate( 'Y/m' ); accessibility_audio_tts_debug_log( 'Audio file paths configured - Path: ' . $this->upload_path . ', URL: ' . $this->upload_url ); } /** * Create .htaccess security file. * * @return void */ private function create_htaccess_file() { $htaccess_content = ' RewriteEngine On RewriteCond %{HTTP_REFERER} !^http://(www\.)?darlowo\.pl [NC] RewriteCond %{HTTP_REFERER} !^$ RewriteRule \.mp3$ - [F] Order Allow,Deny Allow from all '; $target = $this->upload_path . '/.htaccess'; global $wp_filesystem; if ( ! $wp_filesystem ) { require_once ABSPATH . 'wp-admin/includes/file.php'; WP_Filesystem(); } if ( $wp_filesystem ) { $wp_filesystem->put_contents( $target, trim( $htaccess_content ) ); } else { file_put_contents( $target, trim( $htaccess_content ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents } } /** * Get list of required directories. * * @return array Required directories. */ private function get_required_directories() { $upload = wp_upload_dir(); $base = $upload['basedir'] . '/accessibility-audio-tts'; return array( $upload['basedir'], $base, $base . '/' . gmdate( 'Y' ), $base . '/' . gmdate( 'Y/m' ), ); } /** * Ensure single directory exists and is writable. * * @param string $dir Absolute directory path. * @return bool True if ok. */ private function ensure_single_directory( $dir ) { // Create if missing. if ( ! file_exists( $dir ) ) { if ( ! wp_mkdir_p( $dir ) ) { return false; } } // Check writability with helper when available, else fallback. if ( class_exists( '\\Accessibility_Audio_TTS\\API\\File_Validation_Helper' ) ) { return File_Validation_Helper::ensure_directory_writable( $dir ); } if ( function_exists( 'wp_is_writable' ) ) { return wp_is_writable( $dir ); } global $wp_filesystem; if ( ! $wp_filesystem ) { require_once ABSPATH . 'wp-admin/includes/file.php'; WP_Filesystem(); } if ( $wp_filesystem ) { $test_file = rtrim( $dir, '/\\' ) . '/.aat-writable-check'; $wrote = $wp_filesystem->put_contents( $test_file, 'ok' ); if ( ! $wrote ) { return false; } $wp_filesystem->delete( $test_file ); return true; } return false; } }