false ) ); } // Cloudflare if ( has_action( 'cloudflare_purge_everything' ) ) { do_action( 'cloudflare_purge_everything' ); } } // ------------------------------------------------------------------------ // Translations cache functions // ------------------------------------------------------------------------ /** * Clear cached translations * * Deletes the translations transient and clears the website cache. * * @return void */ function wplng_clear_translations_cache() { delete_option( 'wplng_cached_translations' ); wplng_clear_website_cache(); } /** * Clear cached translations when a translation post is trashed or untrashed * * @param int $post_id The ID of the post being trashed or untrashed. * @return void */ function wplng_clear_translations_cache_trash_untrash( $post_id ) { if ( 'wplng_translation' !== get_post_type( $post_id ) ) { return; } wplng_clear_translations_cache(); } // ------------------------------------------------------------------------ // Slug cache functions // ------------------------------------------------------------------------ /** * Clear cached slugs * * Deletes the slugs option and clears the website cache. * * @return void */ function wplng_clear_slugs_cache() { delete_option( 'wplng_cached_slugs' ); wplng_clear_website_cache(); } /** * Clear cached slugs when a slug post is trashed or untrashed * * @param int $post_id The ID of the post being trashed or untrashed. * @return void */ function wplng_clear_slugs_cache_trash_untrash( $post_id ) { if ( 'wplng_slug' !== get_post_type( $post_id ) ) { return; } wplng_clear_slugs_cache(); } // ------------------------------------------------------------------------ // wpLingua file cache functions // ------------------------------------------------------------------------ /** * Write data to a cache file in the wpLingua cache directory. * * Creates the cache directory structure and protection files if needed, * then writes the provided data to the specified file. * * @param string $file_name Relative path to the cache file (from WPLNG_CACHE_DIR). * @param string $data Data to write to the file. * @return int|false Number of bytes written, or false on failure. */ function wplng_put_cache_file( $file_name, $data ) { // Security: prevent path traversal if ( wplng_str_contains( $file_name, '..' ) ) { return false; } // Create main wpLingua cache folder if not exist if ( ! is_dir( WPLNG_CACHE_DIR ) ) { if ( ! wp_mkdir_p( WPLNG_CACHE_DIR ) ) { return false; } } // Create minimal protections in main folder (only once per request) static $main_protection_checked = false; if ( ! $main_protection_checked ) { // Create .htaccess if not exists if ( ! file_exists( WPLNG_CACHE_DIR . '/.htaccess' ) ) { $htaccess = '##############################' . PHP_EOL; $htaccess .= '# File generated by wpLingua #' . PHP_EOL; $htaccess .= '##############################' . PHP_EOL; $htaccess .= PHP_EOL; $htaccess .= '# Disable files listing' . PHP_EOL; $htaccess .= 'Options -Indexes' . PHP_EOL; $htaccess .= PHP_EOL; $htaccess .= '# Deny all HTTP access to this directory' . PHP_EOL; $htaccess .= '' . PHP_EOL; $htaccess .= ' Require all denied' . PHP_EOL; $htaccess .= '' . PHP_EOL; $htaccess .= PHP_EOL; $htaccess .= '' . PHP_EOL; $htaccess .= ' Deny from all' . PHP_EOL; $htaccess .= '' . PHP_EOL; file_put_contents( WPLNG_CACHE_DIR . '/.htaccess', $htaccess ); } // Create an empty index.html file in main folder wplng_create_cache_index_html( WPLNG_CACHE_DIR ); $main_protection_checked = true; } // Create the subfolder if needed $dir = dirname( WPLNG_CACHE_DIR . $file_name ); if ( ! is_dir( $dir ) ) { if ( ! wp_mkdir_p( $dir ) ) { return false; } } // Create index.html in all directories between main path and target directory wplng_create_cache_index_html_recursive( $dir ); // Write the cache file return file_put_contents( WPLNG_CACHE_DIR . $file_name, $data ); } /** * Create an empty index.html file in the specified directory. * * @param string $dir_path Absolute path to the directory. * @return bool True if file exists or was created, false on failure. */ function wplng_create_cache_index_html( $dir_path ) { $index_file = rtrim( $dir_path, '/\\' ) . '/index.html'; if ( file_exists( $index_file ) ) { return true; } return file_put_contents( $index_file, '' ) !== false; } /** * Create index.html files in all directories from WPLNG_CACHE_DIR to the target directory. * * @param string $target_dir Absolute path to the target directory. * @return void */ function wplng_create_cache_index_html_recursive( $target_dir ) { $target_dir = wp_normalize_path( $target_dir ); $base_path = wp_normalize_path( WPLNG_CACHE_DIR ); // Ensure target is within cache path if ( strpos( $target_dir, $base_path ) !== 0 ) { return; } // Get relative path from base $relative_path = substr( $target_dir, strlen( $base_path ) ); $relative_path = trim( $relative_path, '/' ); if ( empty( $relative_path ) ) { return; } // Build each subdirectory and create index.html $parts = explode( '/', $relative_path ); $current_path = $base_path; foreach ( $parts as $part ) { $current_path .= '/' . $part; if ( is_dir( $current_path ) ) { wplng_create_cache_index_html( $current_path ); } } } /** * Read data from a cache file in the wpLingua cache directory. * * @param string $file_name Relative path to the cache file (from WPLNG_CACHE_DIR). * @return string|false File contents, or false if file doesn't exist or isn't readable. */ function wplng_get_cache_file( $file_name ) { // Security: prevent path traversal if ( wplng_str_contains( $file_name, '..' ) ) { return false; } $file_path = WPLNG_CACHE_DIR . $file_name; if ( ! is_readable( $file_path ) ) { return false; } return file_get_contents( $file_path ); } /** * Clear wpLingua cache folder or a specific file/directory. * * @param string|false $file Relative path to delete, or false to delete entire cache folder. * @return bool True on success, false on failure. */ function wplng_clear_folder_cache( $file = false ) { if ( $file === false ) { $path = WPLNG_CACHE_DIR; } else { $path = wp_normalize_path( WPLNG_CACHE_DIR . $file ); // Security: ensure path is within cache directory if ( wplng_str_contains( $file, '..' ) || ! wplng_str_starts_with( $path, wp_normalize_path( WPLNG_CACHE_DIR ) ) ) { return false; } } if ( ! file_exists( $path ) ) { return true; } if ( is_file( $path ) ) { return @unlink( $path ); } return wplng_delete_directory( $path ); } /** * Clear wpLingua cache when WordPress core, plugins, themes or translations are updated. * * Hooked to: * - upgrader_process_complete (core, plugins, themes, translations updates) * - activated_plugin * - deactivated_plugin * - switch_theme * * @param mixed $upgrader_or_plugin WP_Upgrader instance or plugin slug (depends on hook). * @param array $options Array of update data (only for upgrader_process_complete). * @return void */ function wplng_clear_cache_on_update( $upgrader_or_plugin = null, $options = array() ) { // For upgrader_process_complete hook, check update type if ( ! empty( $options ) && is_array( $options ) ) { $type = isset( $options['type'] ) ? $options['type'] : ''; // Clear cache for core, plugin, theme or translation updates if ( in_array( $type, array( 'core', 'plugin', 'theme', 'translation' ), true ) ) { wplng_clear_folder_cache(); } return; } // For other hooks (activated_plugin, deactivated_plugin, switch_theme) wplng_clear_folder_cache(); } /** * Recursively delete a directory and all its contents. * * Only deletes directories within WPLNG_CACHE_DIR for security. * * @param string $dir_path Absolute path to the directory. * @return bool True on success, false on failure. */ function wplng_delete_directory( $dir_path ) { $dir_path = wp_normalize_path( $dir_path ); $base_path = wp_normalize_path( WPLNG_CACHE_DIR ); // Security: ensure path is within cache directory if ( strpos( $dir_path, $base_path ) !== 0 ) { return false; } if ( ! is_dir( $dir_path ) ) { return false; } $items = scandir( $dir_path ); if ( $items === false ) { return false; } foreach ( $items as $item ) { if ( $item === '.' || $item === '..' ) { continue; } $item_path = $dir_path . '/' . $item; if ( is_dir( $item_path ) ) { wplng_delete_directory( $item_path ); } else { @unlink( $item_path ); } } return @rmdir( $dir_path ); }