post_content; // Apply content filters (shortcodes, etc.). // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Using WordPress core filter. $content = apply_filters( 'the_content', $content ); // Remove HTML tags. $content = wp_strip_all_tags( $content ); // Clean up whitespace. $content = preg_replace( '/\s+/', ' ', $content ); $content = trim( $content ); // Remove common unwanted patterns. $patterns = array( '/\[.*?\]/', // Shortcodes that weren't processed. '/\{.*?\}/', // Curly braces content. '/https?:\/\/[^\s]+/', // URLs. '/\b\d{4}-\d{2}-\d{2}\b/', // Dates. ); foreach ( $patterns as $pattern ) { $content = preg_replace( $pattern, '', $content ); } // Final cleanup. $content = preg_replace( '/\s+/', ' ', $content ); $content = trim( $content ); return $content; } /** * Estimate audio duration from text * * @param string $text Text content. * @param float $words_per_minute Speaking rate (default: 150 WPM). * @return int Estimated duration in seconds. * @since 1.0.6 */ public static function estimate_audio_duration( $text, $words_per_minute = 150 ) { $word_count = str_word_count( $text ); $minutes = $word_count / $words_per_minute; return ceil( $minutes * 60 ); } /** * Format duration for display * * @param int $seconds Duration in seconds. * @return string Formatted duration (e.g., "2:30", "1:05:30") * @since 1.0.6 */ public static function format_duration( $seconds ) { if ( $seconds < 0 ) { return '0:00'; } $hours = floor( $seconds / 3600 ); $minutes = floor( ( $seconds % 3600 ) / 60 ); $seconds = $seconds % 60; if ( $hours > 0 ) { return sprintf( '%d:%02d:%02d', $hours, $minutes, $seconds ); } else { return sprintf( '%d:%02d', $minutes, $seconds ); } } /** * Format file size for display * * @param int $bytes File size in bytes. * @param int $precision Decimal precision. * @return string Formatted file size. * @since 1.0.6 */ public static function format_file_size( $bytes, $precision = 2 ) { $units = array( 'B', 'KB', 'MB', 'GB', 'TB', 'PB' ); $units_count = count( $units ); for ( $i = 0; $bytes > 1024 && $i < $units_count - 1; $i++ ) { $bytes /= 1024; } return round( $bytes, $precision ) . ' ' . $units[ $i ]; } /** * Get audio file info * * @param string $file_path Path to audio file. * @return array|false File info array or false on failure. * @since 1.0.6 */ public static function get_audio_file_info( $file_path ) { if ( ! file_exists( $file_path ) ) { return false; } $info = array( 'size' => filesize( $file_path ), 'modified' => filemtime( $file_path ), 'duration' => null, 'format' => null, ); // Get file extension. $extension = strtolower( pathinfo( $file_path, PATHINFO_EXTENSION ) ); $info['format'] = $extension; // Try to get duration using getID3 if available. if ( class_exists( 'getID3' ) ) { try { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase -- Third-party library class name. $getID3 = new \getID3(); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase -- Third-party library object. $file_info = $getID3->analyze( $file_path ); if ( isset( $file_info['playtime_seconds'] ) ) { $info['duration'] = round( $file_info['playtime_seconds'] ); } } catch ( \Exception $e ) { // Intentionally ignore getID3 errors - duration is optional metadata. unset( $e ); } } return $info; } /** * Check if URL is accessible * * @param string $url URL to check. * @param int $timeout Timeout in seconds. * @return bool True if accessible. * @since 1.0.6 */ public static function is_url_accessible( $url, $timeout = 10 ) { $response = wp_remote_head( $url, array( 'timeout' => $timeout, 'sslverify' => false, ) ); if ( is_wp_error( $response ) ) { return false; } $status_code = wp_remote_retrieve_response_code( $response ); return $status_code >= 200 && $status_code < 400; } /** * Generate unique filename * * @param string $prefix Filename prefix. * @param string $extension File extension. * @param string $directory Target directory. * @return string Unique filename. * @since 1.0.6 */ public static function generate_unique_filename( $prefix, $extension, $directory = '' ) { $timestamp = time(); $random = wp_generate_password( 8, false ); $filename = $prefix . '_' . $timestamp . '_' . $random . '.' . $extension; // Ensure uniqueness. if ( ! empty( $directory ) ) { $full_path = trailingslashit( $directory ) . $filename; $counter = 1; while ( file_exists( $full_path ) ) { $filename = $prefix . '_' . $timestamp . '_' . $random . '_' . $counter . '.' . $extension; $full_path = trailingslashit( $directory ) . $filename; ++$counter; } } return $filename; } /** * Create directory if it doesn't exist * * @param string $directory Directory path. * @param int $permissions Directory permissions. * @return bool True on success. * @since 1.0.6 */ public static function ensure_directory_exists( $directory, $permissions = 0755 ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- Required by WordPress filter hook. if ( is_dir( $directory ) ) { return true; } return wp_mkdir_p( $directory ); } /** * Get WordPress upload directory info * * @param string $subdir Subdirectory name. * @return array Upload directory info. * @since 1.0.6 */ public static function get_upload_dir( $subdir = '' ) { $upload_dir = wp_upload_dir(); if ( ! empty( $subdir ) ) { $upload_dir['path'] = trailingslashit( $upload_dir['path'] ) . $subdir; $upload_dir['url'] = trailingslashit( $upload_dir['url'] ) . $subdir; $upload_dir['subdir'] = trailingslashit( $upload_dir['subdir'] ) . $subdir; } return $upload_dir; } /** * Clean up old files * * @param string $directory Directory to clean. * @param int $max_age Maximum age in seconds. * @return int Number of files deleted. * @since 1.0.6 */ public static function cleanup_old_files( $directory, $max_age = WEEK_IN_SECONDS ) { if ( ! is_dir( $directory ) ) { return 0; } $deleted = 0; $current_time = time(); $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $directory, \RecursiveDirectoryIterator::SKIP_DOTS ) ); foreach ( $iterator as $file ) { if ( $file->isFile() ) { $file_age = $current_time - $file->getMTime(); if ( $file_age > $max_age ) { if ( wp_delete_file( $file->getPathname() ) ) { ++$deleted; } } } } return $deleted; } /** * Get system information * * @return array System information. * @since 1.0.6 */ public static function get_system_info() { global $wpdb; $server = is_array( $_SERVER ) ? array_map( 'sanitize_text_field', wp_unslash( $_SERVER ) ) : array(); return array( 'wordpress_version' => get_bloginfo( 'version' ), 'php_version' => PHP_VERSION, 'mysql_version' => $wpdb->db_version(), 'server_software' => $server['SERVER_SOFTWARE'] ?? 'Unknown', 'max_execution_time' => ini_get( 'max_execution_time' ), 'memory_limit' => ini_get( 'memory_limit' ), 'upload_max_filesize' => ini_get( 'upload_max_filesize' ), 'post_max_size' => ini_get( 'post_max_size' ), 'allow_url_fopen' => ini_get( 'allow_url_fopen' ) ? 'Yes' : 'No', 'curl_version' => function_exists( 'curl_version' ) ? curl_version()['version'] : 'Not available', 'openssl_version' => defined( 'OPENSSL_VERSION_TEXT' ) ? OPENSSL_VERSION_TEXT : 'Not available', 'user_agent' => $server['HTTP_USER_AGENT'] ?? 'Unknown', 'timezone' => wp_timezone_string(), 'locale' => get_locale(), 'multisite' => is_multisite() ? 'Yes' : 'No', 'debug_mode' => defined( 'WP_DEBUG' ) && WP_DEBUG ? 'Yes' : 'No', ); } /** * Log system information * * @since 1.0.6 */ public static function log_system_info() { if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) { return; } $info = self::get_system_info(); $log_message = "Accessibility Audio TTS System Info:\n"; foreach ( $info as $key => $value ) { $log_message .= " {$key}: {$value}\n"; } if ( function_exists( 'accessibility_audio_tts_error_log' ) ) { accessibility_audio_tts_error_log( $log_message ); } } /** * Check plugin requirements * * @return array Array of requirement checks. * @since 1.0.6 */ public static function check_requirements() { $requirements = array( 'php_version' => array( 'required' => ACCESSIBILITY_AUDIO_TTS_MIN_PHP_VERSION, 'current' => PHP_VERSION, 'met' => version_compare( PHP_VERSION, ACCESSIBILITY_AUDIO_TTS_MIN_PHP_VERSION, '>=' ), ), 'wordpress_version' => array( 'required' => ACCESSIBILITY_AUDIO_TTS_MIN_WP_VERSION, 'current' => get_bloginfo( 'version' ), 'met' => version_compare( get_bloginfo( 'version' ), ACCESSIBILITY_AUDIO_TTS_MIN_WP_VERSION, '>=' ), ), 'curl_extension' => array( 'required' => 'Yes', 'current' => function_exists( 'curl_init' ) ? 'Yes' : 'No', 'met' => function_exists( 'curl_init' ), ), 'json_extension' => array( 'required' => 'Yes', 'current' => function_exists( 'json_encode' ) ? 'Yes' : 'No', 'met' => function_exists( 'json_encode' ), ), 'mbstring_extension' => array( 'required' => 'Yes', 'current' => function_exists( 'mb_strlen' ) ? 'Yes' : 'No', 'met' => function_exists( 'mb_strlen' ), ), 'allow_url_fopen' => array( 'required' => 'Yes', 'current' => ini_get( 'allow_url_fopen' ) ? 'Yes' : 'No', 'met' => ini_get( 'allow_url_fopen' ), ), ); return $requirements; } /** * Get unmet requirements * * @return array Array of unmet requirements. * @since 1.0.6 */ public static function get_unmet_requirements() { $requirements = self::check_requirements(); return array_filter( $requirements, function ( $requirement ) { return ! $requirement['met']; } ); } /** * Display requirements notice * * @since 1.0.6 */ public static function display_requirements_notice() { $unmet = self::get_unmet_requirements(); if ( empty( $unmet ) ) { return; } add_action( 'admin_notices', function () use ( $unmet ) { echo '
';
echo '' . esc_html__( 'Accessibility Audio TTS: System Requirements Not Met', 'accessibility-audio-tts-text-to-speech-for-articles' ) . '
';
foreach ( $unmet as $key => $requirement ) {
/* translators: 1: requirement name, 2: required value, 3: current value */
printf(
/* translators: auto */
esc_html__( '%1$s: Required %2$s, Current %3$s', 'accessibility-audio-tts-text-to-speech-for-articles' ) . '
',
esc_html( ucwords( str_replace( '_', ' ', $key ) ) ),
esc_html( $requirement['required'] ),
esc_html( $requirement['current'] )
);
}
echo '