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;
}
}