PluginProbe ʕ •ᴥ•ʔ
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI / 3.5.2
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI v3.5.2
3.5.2 3.5.1 3.5.0 3.4.8 3.4.7 3.4.6 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.5.1 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.10 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.6.1 1.6.7 1.7.0 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.2 1.7.2.1 1.7.2.2 1.7.3 1.7.4 1.7.5 1.7.5.1 1.7.5.2 1.7.6 1.7.7 1.7.7.1 1.7.7.2 1.7.8 1.7.9 1.8.0 1.8.0.1 1.8.1 1.8.2 1.8.2.1 1.8.2.2 1.8.2.3 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.0.1 1.9.1 1.9.2 1.9.3 1.9.4 1.9.4.1 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.0.1 2.0.1 2.0.2 2.0.3 2.0.3.1 2.0.4 2.0.4.1 2.0.5 2.0.6 2.0.7 2.0.8 2.0.8.1 2.0.9 3.0.0 3.0.0.1 3.0.1 3.0.2 3.0.3 3.0.3.1 3.0.4 3.0.4.1 3.0.4.2 3.0.5 3.0.5.1 3.0.5.2 3.0.6 3.0.6.1 3.0.7.1 3.0.8 3.0.8.1 3.0.9 3.0.9.1 3.0.9.2 3.0.9.3 3.0.9.4 3.0.9.5 3.1.0 3.1.1 3.1.2 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.3.0 3.4.0 3.4.1 3.4.2 3.4.2.1 3.4.3 3.4.4 3.4.5 trunk 1.0 1.0.1 1.0.2 1.0.3
everest-forms / includes / log-handlers / class-evf-log-handler-file.php
everest-forms / includes / log-handlers Last commit date
class-evf-log-handler-file.php 1 year ago
class-evf-log-handler-file.php
462 lines
1 <?php
2 /**
3 * Handles log entries by writing to a file.
4 *
5 * @package EverestForms/Classes/Log_Handlers
6 * @version 1.0.0
7 */
8
9 defined( 'ABSPATH' ) || exit;
10
11 /**
12 * Handles log entries by writing to a file.
13 */
14 class EVF_Log_Handler_File extends EVF_Log_Handler {
15
16 /**
17 * Stores open file handles.
18 *
19 * @var array
20 */
21 protected $handles = array();
22
23 /**
24 * File size limit for log files in bytes.
25 *
26 * @var int
27 */
28 protected $log_size_limit;
29
30 /**
31 * Cache logs that could not be written.
32 *
33 * If a log is written too early in the request, pluggable functions may be unavailable. These
34 * logs will be cached and written on 'plugins_loaded' action.
35 *
36 * @var array
37 */
38 protected $cached_logs = array();
39
40 /**
41 * Constructor for the logger.
42 *
43 * @param int $log_size_limit Optional. Size limit for log files. Default 5mb.
44 */
45 public function __construct( $log_size_limit = null ) {
46 if ( null === $log_size_limit ) {
47 $log_size_limit = 5 * 1024 * 1024;
48 }
49
50 $this->log_size_limit = apply_filters( 'everest_forms_log_file_size_limit', $log_size_limit );
51
52 add_action( 'plugins_loaded', array( $this, 'write_cached_logs' ) );
53 }
54
55 /**
56 * Destructor.
57 *
58 * Cleans up open file handles.
59 */
60 public function __destruct() {
61 foreach ( $this->handles as $handle ) {
62 if ( is_resource( $handle ) ) {
63 fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
64 }
65 }
66 }
67
68 /**
69 * Handle a log entry.
70 *
71 * @param int $timestamp Log timestamp.
72 * @param string $level emergency|alert|critical|error|warning|notice|info|debug.
73 * @param string $message Log message.
74 * @param array $context {
75 * Additional information for log handlers.
76 *
77 * @type string $source Optional. Determines log file to write to. Default 'log'.
78 * @type bool $_legacy Optional. Default false. True to use outdated log format
79 * originally used in deprecated EVF_Logger::add calls.
80 * }
81 *
82 * @return bool False if value was not handled and true if value was handled.
83 */
84 public function handle( $timestamp, $level, $message, $context ) {
85 if ( isset( $context['source'] ) && $context['source'] ) {
86 $handle = $context['source'];
87 } else {
88 $handle = 'log';
89 }
90
91 $entry = self::format_entry( $timestamp, $level, $message, $context );
92
93 return $this->add( $entry, $handle );
94 }
95
96 /**
97 * Builds a log entry text from timestamp, level and message.
98 *
99 * @param int $timestamp Log timestamp.
100 * @param string $level emergency|alert|critical|error|warning|notice|info|debug.
101 * @param string $message Log message.
102 * @param array $context Additional information for log handlers.
103 *
104 * @return string Formatted log entry.
105 */
106 protected static function format_entry( $timestamp, $level, $message, $context ) {
107 if ( isset( $context['_legacy'] ) && true === $context['_legacy'] ) {
108 if ( isset( $context['source'] ) && $context['source'] ) {
109 $handle = $context['source'];
110 } else {
111 $handle = 'log';
112 }
113 $message = apply_filters( 'everest_forms_logger_add_message', $message, $handle );
114 $time = date_i18n( 'm-d-Y @ H:i:s' );
115 $entry = "{$time} - {$message}";
116 } else {
117 $entry = parent::format_entry( $timestamp, $level, $message, $context );
118 }
119
120 return $entry;
121 }
122
123 /**
124 * Open log file for writing.
125 *
126 * @param string $handle Log handle.
127 * @param string $mode Optional. File mode. Default 'a'.
128 * @return bool Success.
129 */
130 protected function open( $handle, $mode = 'a' ) {
131 if ( $this->is_open( $handle ) ) {
132 return true;
133 }
134
135 $file = self::get_log_file_path( $handle );
136
137 if ( ! $file ) {
138 return false;
139 }
140
141 if ( ! file_exists( $file ) ) {
142 $temphandle = @fopen( $file, 'w+' ); // @codingStandardsIgnoreLine
143 if ( ! $temphandle ) {
144 return false;
145 }
146
147 @fclose( $temphandle ); // @codingStandardsIgnoreLine
148
149 if ( defined( 'FS_CHMOD_FILE' ) ) {
150 @chmod( $file, FS_CHMOD_FILE ); // @codingStandardsIgnoreLine
151 }
152 }
153
154 $resource = @fopen( $file, $mode ); // @codingStandardsIgnoreLine
155 if ( ! $resource ) {
156 return false;
157 }
158
159 $this->handles[ $handle ] = $resource;
160 return true;
161 }
162
163
164 /**
165 * Check if a handle is open.
166 *
167 * @param string $handle Log handle.
168 * @return bool True if $handle is open.
169 */
170 protected function is_open( $handle ) {
171 return array_key_exists( $handle, $this->handles ) && is_resource( $this->handles[ $handle ] );
172 }
173
174 /**
175 * Close a handle.
176 *
177 * @param string $handle Log handle.
178 * @return bool success
179 */
180 protected function close( $handle ) {
181 $result = false;
182
183 if ( $this->is_open( $handle ) ) {
184 $result = fclose( $this->handles[ $handle ] ); // @codingStandardsIgnoreLine
185 unset( $this->handles[ $handle ] );
186 }
187
188 return $result;
189 }
190
191 /**
192 * Add a log entry to chosen file.
193 *
194 * @param string $entry Log entry text.
195 * @param string $handle Log entry handle.
196 *
197 * @return bool True if write was successful.
198 */
199 protected function add( $entry, $handle ) {
200 $result = false;
201
202 if ( $this->should_rotate( $handle ) ) {
203 $this->log_rotate( $handle );
204 }
205
206 if ( $this->open( $handle ) && is_resource( $this->handles[ $handle ] ) ) {
207 $result = fwrite( $this->handles[ $handle ], $entry . PHP_EOL ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite
208 } else {
209 $this->cache_log( $entry, $handle );
210 }
211
212 return false !== $result;
213 }
214
215 /**
216 * Clear entries from chosen file.
217 *
218 * @param string $handle Log handle.
219 *
220 * @return bool
221 */
222 public function clear( $handle ) {
223 $result = false;
224
225 // Close the file if it's already open.
226 $this->close( $handle );
227
228 /**
229 * $this->open( $handle, 'w' ) == Open the file for writing only. Place the file pointer at
230 * the beginning of the file, and truncate the file to zero length.
231 */
232 if ( $this->open( $handle, 'w' ) && is_resource( $this->handles[ $handle ] ) ) {
233 $result = true;
234 }
235
236 do_action( 'everest_forms_log_clear', $handle );
237
238 return $result;
239 }
240
241 /**
242 * Remove/delete the chosen file.
243 *
244 * @param string $handle Log handle.
245 *
246 * @return bool
247 */
248 public function remove( $handle ) {
249 $removed = false;
250 $logs = $this->get_log_files();
251 $handle = sanitize_title( $handle );
252
253 if ( isset( $logs[ $handle ] ) && $logs[ $handle ] ) {
254 $file = realpath( trailingslashit( EVF_LOG_DIR ) . $logs[ $handle ] );
255 if ( 0 === stripos( $file, realpath( trailingslashit( EVF_LOG_DIR ) ) ) && is_file( $file ) && is_writable( $file ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable
256 $this->close( $file ); // Close first to be certain no processes keep it alive after it is unlinked.
257 $removed = unlink( $file ); // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_unlink
258 }
259 do_action( 'everest_forms_log_remove', $handle, $removed );
260 }
261 return $removed;
262 }
263
264 /**
265 * Remove/delete all log files.
266 *
267 * @return bool
268 */
269 public function remove_all() {
270 $removed = false;
271 $logs = $this->get_log_files();
272
273 if ( count( $logs ) ) {
274 foreach ( $logs as $key => $log ) {
275 $file = realpath( trailingslashit( EVF_LOG_DIR ) . $log );
276 if ( 0 === stripos( $file, realpath( trailingslashit( EVF_LOG_DIR ) ) ) && is_file( $file ) && is_writable( $file ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable
277 $this->close( $file ); // Close first to be certain no processes keep it alive after it is unlinked.
278 $removed = unlink( $file ); // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_unlink
279 }
280 }
281 }
282 return $removed;
283 }
284
285 /**
286 * Check if log file should be rotated.
287 *
288 * Compares the size of the log file to determine whether it is over the size limit.
289 *
290 * @param string $handle Log handle.
291 * @return bool True if if should be rotated.
292 */
293 protected function should_rotate( $handle ) {
294 $file = self::get_log_file_path( $handle );
295 if ( $file ) {
296 if ( $this->is_open( $handle ) ) {
297 $file_stat = fstat( $this->handles[ $handle ] );
298 return $file_stat['size'] > $this->log_size_limit;
299 } elseif ( file_exists( $file ) ) {
300 return filesize( $file ) > $this->log_size_limit;
301 } else {
302 return false;
303 }
304 } else {
305 return false;
306 }
307 }
308
309 /**
310 * Rotate log files.
311 *
312 * Logs are rotated by prepending '.x' to the '.log' suffix.
313 * The current log plus 10 historical logs are maintained.
314 * For example:
315 * base.9.log -> [ REMOVED ]
316 * base.8.log -> base.9.log
317 * ...
318 * base.0.log -> base.1.log
319 * base.log -> base.0.log
320 *
321 * @param string $handle Log handle.
322 */
323 protected function log_rotate( $handle ) {
324 for ( $i = 8; $i >= 0; $i-- ) {
325 $this->increment_log_infix( $handle, $i );
326 }
327 $this->increment_log_infix( $handle );
328 }
329
330 /**
331 * Increment a log file suffix.
332 *
333 * @param string $handle Log handle.
334 * @param null|int $number Optional. Default null. Log suffix number to be incremented.
335 * @return bool True if increment was successful, otherwise false.
336 */
337 protected function increment_log_infix( $handle, $number = null ) {
338 if ( null === $number ) {
339 $suffix = '';
340 $next_suffix = '.0';
341 } else {
342 $suffix = '.' . $number;
343 $next_suffix = '.' . ( $number + 1 );
344 }
345
346 $rename_from = self::get_log_file_path( "{$handle}{$suffix}" );
347 $rename_to = self::get_log_file_path( "{$handle}{$next_suffix}" );
348
349 if ( $this->is_open( $rename_from ) ) {
350 $this->close( $rename_from );
351 }
352
353 if ( is_writable( $rename_from ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable
354 return rename( $rename_from, $rename_to ); // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_rename
355 } else {
356 return false;
357 }
358 }
359
360 /**
361 * Get a log file path.
362 *
363 * @param string $handle Log name.
364 * @return bool|string The log file path or false if path cannot be determined.
365 */
366 public static function get_log_file_path( $handle ) {
367 if ( function_exists( 'wp_hash' ) ) {
368 return trailingslashit( EVF_LOG_DIR ) . self::get_log_file_name( $handle );
369 } else {
370 evf_doing_it_wrong( __METHOD__, __( 'This method should not be called before plugins_loaded.', 'everest-forms' ), '1.2' );
371 return false;
372 }
373 }
374
375 /**
376 * Get a log file name.
377 *
378 * File names consist of the handle, followed by the date, followed by a hash, .log.
379 *
380 * @since 3.3
381 * @param string $handle Log name.
382 * @return bool|string The log file name or false if cannot be determined.
383 */
384 public static function get_log_file_name( $handle ) {
385 if ( function_exists( 'wp_hash' ) ) {
386 $date_suffix = date( 'Y-m-d', time() ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
387 $hash_suffix = wp_hash( $handle );
388 return sanitize_file_name( implode( '-', array( $handle, $date_suffix, $hash_suffix ) ) . '.log' );
389 } else {
390 evf_doing_it_wrong( __METHOD__, esc_html__( 'This method should not be called before plugins_loaded.', 'everest-forms' ), '1.2' );
391 return false;
392 }
393 }
394
395 /**
396 * Cache log to write later.
397 *
398 * @param string $entry Log entry text.
399 * @param string $handle Log entry handle.
400 */
401 protected function cache_log( $entry, $handle ) {
402 $this->cached_logs[] = array(
403 'entry' => $entry,
404 'handle' => $handle,
405 );
406 }
407
408 /**
409 * Write cached logs.
410 */
411 public function write_cached_logs() {
412 foreach ( $this->cached_logs as $log ) {
413 $this->add( $log['entry'], $log['handle'] );
414 }
415 }
416
417 /**
418 * Delete all logs older than a defined timestamp.
419 *
420 * @since 1.6.2
421 * @param integer $timestamp Timestamp to delete logs before.
422 */
423 public static function delete_logs_before_timestamp( $timestamp = 0 ) {
424 if ( ! $timestamp ) {
425 return;
426 }
427
428 $log_files = self::get_log_files();
429
430 foreach ( $log_files as $log_file ) {
431 $last_modified = filemtime( trailingslashit( EVF_LOG_DIR ) . $log_file );
432
433 if ( $last_modified < $timestamp ) {
434 @unlink( trailingslashit( EVF_LOG_DIR ) . $log_file ); // @codingStandardsIgnoreLine.
435 }
436 }
437 }
438
439 /**
440 * Get all log files in the log directory.
441 *
442 * @since 1.6.2
443 * @return array
444 */
445 public static function get_log_files() {
446 $files = @scandir( EVF_LOG_DIR ); // @codingStandardsIgnoreLine.
447 $result = array();
448
449 if ( ! empty( $files ) ) {
450 foreach ( $files as $key => $value ) {
451 if ( ! in_array( $value, array( '.', '..' ), true ) ) {
452 if ( ! is_dir( $value ) && strstr( $value, '.log' ) ) {
453 $result[ sanitize_title( $value ) ] = $value;
454 }
455 }
456 }
457 }
458
459 return $result;
460 }
461 }
462