data
1 year ago
engines
10 months ago
exceptions
1 year ago
modules
10 months ago
query
10 months ago
rest
10 months ago
services
10 months ago
admin.php
10 months ago
api.php
10 months ago
core.php
10 months ago
discussion.php
1 year ago
event.php
1 year ago
init.php
11 months ago
logging.php
1 year ago
reply.php
10 months ago
rest.php
10 months ago
logging.php
281 lines
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * Class Meow_MWAI_Logging |
| 5 | * |
| 6 | * A logging utility that uses the WordPress Filesystem API for storage, |
| 7 | * with fallback to PHP error_log when necessary. |
| 8 | */ |
| 9 | class Meow_MWAI_Logging { |
| 10 | private static $plugin_name; |
| 11 | private static $option_name; |
| 12 | private static $log_file_path; |
| 13 | private static $fs; |
| 14 | private static $log_count = 0; |
| 15 | private static $rotate_check_frequency = 10; |
| 16 | private static $max_log_size = 5 * 1024 * 1024; // 5 MB |
| 17 | |
| 18 | /** |
| 19 | * Initialize the logger. |
| 20 | * |
| 21 | * @param string $option_name Option key for settings. |
| 22 | * @param string $plugin_name Plugin identifier for error log prefix. |
| 23 | */ |
| 24 | public static function init( $option_name, $plugin_name ) { |
| 25 | self::$plugin_name = $plugin_name; |
| 26 | self::$option_name = $option_name; |
| 27 | |
| 28 | // Attempt to use WP_Filesystem only if the 'direct' method is available. |
| 29 | if ( !function_exists( 'WP_Filesystem' ) ) { |
| 30 | require_once ABSPATH . 'wp-admin/includes/file.php'; |
| 31 | } |
| 32 | |
| 33 | if ( function_exists( 'get_filesystem_method' ) && 'direct' === get_filesystem_method() ) { |
| 34 | // If 'direct' is allowed, try to initialize the filesystem (no credentials prompt). |
| 35 | if ( WP_Filesystem() ) { |
| 36 | global $wp_filesystem; |
| 37 | self::$fs = $wp_filesystem; |
| 38 | } |
| 39 | else { |
| 40 | // Could not initialize |
| 41 | error_log( self::$plugin_name . ': Could not init direct WP_Filesystem. Falling back to error_log only.' ); |
| 42 | self::$fs = null; |
| 43 | } |
| 44 | } |
| 45 | else { |
| 46 | // Not 'direct' or not available; skip filesystem usage |
| 47 | self::$fs = null; |
| 48 | } |
| 49 | |
| 50 | // Attempt to determine or create the log file path |
| 51 | self::$log_file_path = self::get_logs_path( true ); |
| 52 | } |
| 53 | |
| 54 | /** |
| 55 | * Determine or create the log file path using WP_Filesystem. |
| 56 | * |
| 57 | * @param bool $create Whether to generate a new file if none exists. |
| 58 | * @return string|false Path to log file or false if unavailable. |
| 59 | */ |
| 60 | private static function get_logs_path( $create = false ) { |
| 61 | $options = get_option( self::$option_name, null ); |
| 62 | if ( is_null( $options ) ) { |
| 63 | return null; |
| 64 | } |
| 65 | |
| 66 | // If we don't have a filesystem reference, we can't create or write a file |
| 67 | if ( empty( self::$fs ) ) { |
| 68 | return null; |
| 69 | } |
| 70 | |
| 71 | $path = empty( $options['logs_path'] ) ? '' : $options['logs_path']; |
| 72 | |
| 73 | if ( $path && self::$fs->exists( $path ) ) { |
| 74 | return $path; |
| 75 | } |
| 76 | |
| 77 | if ( !$create ) { |
| 78 | return null; |
| 79 | } |
| 80 | |
| 81 | $uploads = wp_upload_dir(); |
| 82 | $base_dir = trailingslashit( $uploads['basedir'] ); |
| 83 | |
| 84 | if ( !self::$fs->is_dir( $base_dir ) ) { |
| 85 | self::$fs->mkdir( $base_dir ); |
| 86 | } |
| 87 | |
| 88 | // Adjust MWAI_PREFIX to whatever your actual constant or value is |
| 89 | $filename = MWAI_PREFIX . '_' . self::random_ascii_chars() . '.log'; |
| 90 | $new_path = $base_dir . $filename; |
| 91 | |
| 92 | self::$fs->put_contents( $new_path, '', FS_CHMOD_FILE ); |
| 93 | |
| 94 | $options['logs_path'] = $new_path; |
| 95 | update_option( self::$option_name, $options ); |
| 96 | |
| 97 | return $new_path; |
| 98 | } |
| 99 | |
| 100 | /** |
| 101 | * Check if logging is enabled via plugin options and FS availability. |
| 102 | * |
| 103 | * @return bool |
| 104 | */ |
| 105 | private static function is_logging_enabled() { |
| 106 | $options = get_option( self::$option_name, null ); |
| 107 | if ( is_null( $options ) ) { |
| 108 | return false; |
| 109 | } |
| 110 | |
| 111 | $module_devtools = empty( $options['module_devtools'] ) ? false : $options['module_devtools']; |
| 112 | $server_debug_mode = empty( $options['server_debug_mode'] ) ? false : $options['server_debug_mode']; |
| 113 | |
| 114 | return ( $module_devtools && $server_debug_mode && !empty( self::$fs ) ); |
| 115 | } |
| 116 | |
| 117 | /** |
| 118 | * Internal log writer. Appends to file and/or error_log. |
| 119 | */ |
| 120 | private static function add( $message = null, $icon = '', $error_log = false ) { |
| 121 | $date = date( 'Y-m-d H:i:s' ); |
| 122 | $message = is_string( $message ) ? strip_tags( $message ) : $message; |
| 123 | |
| 124 | if ( empty( $message ) ) { |
| 125 | $entry = "\n"; |
| 126 | } |
| 127 | else if ( !empty( $icon ) ) { |
| 128 | $entry = "$date: $icon $message\n"; |
| 129 | } |
| 130 | else { |
| 131 | $entry = "$date: $message\n"; |
| 132 | } |
| 133 | |
| 134 | // Write to file if enabled and if a log file path exists |
| 135 | if ( self::is_logging_enabled() && self::$log_file_path ) { |
| 136 | if ( self::$fs->exists( self::$log_file_path ) ) { |
| 137 | $current = self::$fs->get_contents( self::$log_file_path ); |
| 138 | self::$fs->put_contents( self::$log_file_path, $current . $entry, FS_CHMOD_FILE ); |
| 139 | } |
| 140 | else { |
| 141 | self::$fs->put_contents( self::$log_file_path, $entry, FS_CHMOD_FILE ); |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | // Always send to PHP error_log if $error_log is true |
| 146 | if ( $error_log && !empty( $message ) ) { |
| 147 | \error_log( self::$plugin_name . ": $message" ); |
| 148 | } |
| 149 | |
| 150 | self::$log_count++; |
| 151 | |
| 152 | if ( self::$log_count >= self::$rotate_check_frequency ) { |
| 153 | self::maybe_rotate_log(); |
| 154 | self::$log_count = 0; |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | /** |
| 159 | * Logs a general message. |
| 160 | * |
| 161 | * @param string $message The message to log. |
| 162 | * @param string $icon Optional icon to prepend. |
| 163 | */ |
| 164 | public static function log( $message = null, $icon = '' ) { |
| 165 | self::add( $message, $icon ); |
| 166 | } |
| 167 | |
| 168 | /** |
| 169 | * Logs a warning message. |
| 170 | * |
| 171 | * @param string $message The warning message to log. |
| 172 | * @param string $icon Optional icon to prepend (default ⚠️). |
| 173 | */ |
| 174 | public static function warn( $message = null, $icon = '⚠️' ) { |
| 175 | self::add( $message, $icon ); |
| 176 | } |
| 177 | |
| 178 | /** |
| 179 | * Logs an error message and sends to PHP error_log. |
| 180 | * |
| 181 | * @param string $message The error message to log. |
| 182 | * @param string $icon Optional icon to prepend (default ❌). |
| 183 | */ |
| 184 | public static function error( $message = null, $icon = '❌' ) { |
| 185 | self::add( $message, $icon, true ); |
| 186 | } |
| 187 | |
| 188 | /** |
| 189 | * Logs a deprecated feature notice. |
| 190 | * |
| 191 | * @param string $message The message to log. |
| 192 | */ |
| 193 | public static function deprecated( $message = null ) { |
| 194 | self::add( $message, '🚨', true ); |
| 195 | } |
| 196 | |
| 197 | /** |
| 198 | * Clears the log file and resets the option. |
| 199 | */ |
| 200 | public static function clear() { |
| 201 | if ( self::$fs && self::$log_file_path && self::$fs->exists( self::$log_file_path ) ) { |
| 202 | self::$fs->delete( self::$log_file_path ); |
| 203 | $options = get_option( self::$option_name, null ); |
| 204 | $options['logs_path'] = ''; |
| 205 | update_option( self::$option_name, $options ); |
| 206 | self::$log_file_path = ''; |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | /** |
| 211 | * Retrieves the log contents in reverse order (newest first). |
| 212 | * |
| 213 | * @return string |
| 214 | */ |
| 215 | public static function get() { |
| 216 | if ( self::$fs && self::$log_file_path && self::$fs->exists( self::$log_file_path ) ) { |
| 217 | $content = self::$fs->get_contents( self::$log_file_path ); |
| 218 | $lines = explode( "\n", $content ); |
| 219 | $lines = array_filter( $lines ); |
| 220 | $lines = array_reverse( $lines ); |
| 221 | |
| 222 | return implode( "\n", $lines ); |
| 223 | } |
| 224 | |
| 225 | return 'Empty log file.'; |
| 226 | } |
| 227 | |
| 228 | /** |
| 229 | * Checks file size and rotates if exceeding maximum. |
| 230 | */ |
| 231 | private static function maybe_rotate_log() { |
| 232 | if ( empty( self::$fs ) || empty( self::$log_file_path ) ) { |
| 233 | return; |
| 234 | } |
| 235 | |
| 236 | if ( self::$fs->exists( self::$log_file_path ) ) { |
| 237 | $size = self::$fs->size( self::$log_file_path ); |
| 238 | |
| 239 | if ( $size > self::$max_log_size ) { |
| 240 | $info = pathinfo( self::$log_file_path ); |
| 241 | $archived = $info['dirname'] . '/' . $info['filename'] . '_' . date( 'Y-m-d_H-i-s' ) . '.' . $info['extension']; |
| 242 | |
| 243 | self::$fs->move( self::$log_file_path, $archived, true ); |
| 244 | self::$fs->put_contents( self::$log_file_path, '', FS_CHMOD_FILE ); |
| 245 | } |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | /** |
| 250 | * Generates a random ASCII string. |
| 251 | * |
| 252 | * @param int $length String length. |
| 253 | * @return string |
| 254 | */ |
| 255 | private static function random_ascii_chars( $length = 8 ) { |
| 256 | $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; |
| 257 | $result = ''; |
| 258 | |
| 259 | for ( $i = 0; $i < $length; $i++ ) { |
| 260 | $result .= $characters[ mt_rand( 0, strlen( $characters ) - 1 ) ]; |
| 261 | } |
| 262 | |
| 263 | return $result; |
| 264 | } |
| 265 | |
| 266 | /** |
| 267 | * Shortens a string to a specified length, adding ellipsis if needed. |
| 268 | * |
| 269 | * @param int $length String length. |
| 270 | * @return string |
| 271 | */ |
| 272 | public static function shorten( $string, $length = 50 ) { |
| 273 | if ( strlen( $string ) > $length ) { |
| 274 | $string = rtrim( $string, " \t\n\r\0\x0B,." ); |
| 275 | $string = substr( $string, 0, $length - 3 ) . '...'; |
| 276 | } |
| 277 | |
| 278 | return $string; |
| 279 | } |
| 280 | } |
| 281 |