PluginProbe ʕ •ᴥ•ʔ
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI / 2.0.3
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI v2.0.3
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 4 years ago
class-evf-log-handler-file.php
456 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 if ( ! file_exists( $file ) ) {
139 $temphandle = @fopen( $file, 'w+' ); // @codingStandardsIgnoreLine
140 @fclose( $temphandle ); // @codingStandardsIgnoreLine
141
142 if ( defined( 'FS_CHMOD_FILE' ) ) {
143 @chmod( $file, FS_CHMOD_FILE ); // @codingStandardsIgnoreLine
144 }
145 }
146
147 $resource = @fopen( $file, $mode ); // @codingStandardsIgnoreLine
148
149 if ( $resource ) {
150 $this->handles[ $handle ] = $resource;
151 return true;
152 }
153 }
154
155 return false;
156 }
157
158 /**
159 * Check if a handle is open.
160 *
161 * @param string $handle Log handle.
162 * @return bool True if $handle is open.
163 */
164 protected function is_open( $handle ) {
165 return array_key_exists( $handle, $this->handles ) && is_resource( $this->handles[ $handle ] );
166 }
167
168 /**
169 * Close a handle.
170 *
171 * @param string $handle Log handle.
172 * @return bool success
173 */
174 protected function close( $handle ) {
175 $result = false;
176
177 if ( $this->is_open( $handle ) ) {
178 $result = fclose( $this->handles[ $handle ] ); // @codingStandardsIgnoreLine
179 unset( $this->handles[ $handle ] );
180 }
181
182 return $result;
183 }
184
185 /**
186 * Add a log entry to chosen file.
187 *
188 * @param string $entry Log entry text.
189 * @param string $handle Log entry handle.
190 *
191 * @return bool True if write was successful.
192 */
193 protected function add( $entry, $handle ) {
194 $result = false;
195
196 if ( $this->should_rotate( $handle ) ) {
197 $this->log_rotate( $handle );
198 }
199
200 if ( $this->open( $handle ) && is_resource( $this->handles[ $handle ] ) ) {
201 $result = fwrite( $this->handles[ $handle ], $entry . PHP_EOL ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite
202 } else {
203 $this->cache_log( $entry, $handle );
204 }
205
206 return false !== $result;
207 }
208
209 /**
210 * Clear entries from chosen file.
211 *
212 * @param string $handle Log handle.
213 *
214 * @return bool
215 */
216 public function clear( $handle ) {
217 $result = false;
218
219 // Close the file if it's already open.
220 $this->close( $handle );
221
222 /**
223 * $this->open( $handle, 'w' ) == Open the file for writing only. Place the file pointer at
224 * the beginning of the file, and truncate the file to zero length.
225 */
226 if ( $this->open( $handle, 'w' ) && is_resource( $this->handles[ $handle ] ) ) {
227 $result = true;
228 }
229
230 do_action( 'everest_forms_log_clear', $handle );
231
232 return $result;
233 }
234
235 /**
236 * Remove/delete the chosen file.
237 *
238 * @param string $handle Log handle.
239 *
240 * @return bool
241 */
242 public function remove( $handle ) {
243 $removed = false;
244 $logs = $this->get_log_files();
245 $handle = sanitize_title( $handle );
246
247 if ( isset( $logs[ $handle ] ) && $logs[ $handle ] ) {
248 $file = realpath( trailingslashit( EVF_LOG_DIR ) . $logs[ $handle ] );
249 if ( 0 === stripos( $file, realpath( trailingslashit( EVF_LOG_DIR ) ) ) && is_file( $file ) && is_writable( $file ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable
250 $this->close( $file ); // Close first to be certain no processes keep it alive after it is unlinked.
251 $removed = unlink( $file ); // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_unlink
252 }
253 do_action( 'everest_forms_log_remove', $handle, $removed );
254 }
255 return $removed;
256 }
257
258 /**
259 * Remove/delete all log files.
260 *
261 * @return bool
262 */
263 public function remove_all() {
264 $removed = false;
265 $logs = $this->get_log_files();
266
267 if ( count( $logs ) ) {
268 foreach ( $logs as $key => $log ) {
269 $file = realpath( trailingslashit( EVF_LOG_DIR ) . $log );
270 if ( 0 === stripos( $file, realpath( trailingslashit( EVF_LOG_DIR ) ) ) && is_file( $file ) && is_writable( $file ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable
271 $this->close( $file ); // Close first to be certain no processes keep it alive after it is unlinked.
272 $removed = unlink( $file ); // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_unlink
273 }
274 }
275 }
276 return $removed;
277 }
278
279 /**
280 * Check if log file should be rotated.
281 *
282 * Compares the size of the log file to determine whether it is over the size limit.
283 *
284 * @param string $handle Log handle.
285 * @return bool True if if should be rotated.
286 */
287 protected function should_rotate( $handle ) {
288 $file = self::get_log_file_path( $handle );
289 if ( $file ) {
290 if ( $this->is_open( $handle ) ) {
291 $file_stat = fstat( $this->handles[ $handle ] );
292 return $file_stat['size'] > $this->log_size_limit;
293 } elseif ( file_exists( $file ) ) {
294 return filesize( $file ) > $this->log_size_limit;
295 } else {
296 return false;
297 }
298 } else {
299 return false;
300 }
301 }
302
303 /**
304 * Rotate log files.
305 *
306 * Logs are rotated by prepending '.x' to the '.log' suffix.
307 * The current log plus 10 historical logs are maintained.
308 * For example:
309 * base.9.log -> [ REMOVED ]
310 * base.8.log -> base.9.log
311 * ...
312 * base.0.log -> base.1.log
313 * base.log -> base.0.log
314 *
315 * @param string $handle Log handle.
316 */
317 protected function log_rotate( $handle ) {
318 for ( $i = 8; $i >= 0; $i-- ) {
319 $this->increment_log_infix( $handle, $i );
320 }
321 $this->increment_log_infix( $handle );
322 }
323
324 /**
325 * Increment a log file suffix.
326 *
327 * @param string $handle Log handle.
328 * @param null|int $number Optional. Default null. Log suffix number to be incremented.
329 * @return bool True if increment was successful, otherwise false.
330 */
331 protected function increment_log_infix( $handle, $number = null ) {
332 if ( null === $number ) {
333 $suffix = '';
334 $next_suffix = '.0';
335 } else {
336 $suffix = '.' . $number;
337 $next_suffix = '.' . ( $number + 1 );
338 }
339
340 $rename_from = self::get_log_file_path( "{$handle}{$suffix}" );
341 $rename_to = self::get_log_file_path( "{$handle}{$next_suffix}" );
342
343 if ( $this->is_open( $rename_from ) ) {
344 $this->close( $rename_from );
345 }
346
347 if ( is_writable( $rename_from ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable
348 return rename( $rename_from, $rename_to ); // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_rename
349 } else {
350 return false;
351 }
352 }
353
354 /**
355 * Get a log file path.
356 *
357 * @param string $handle Log name.
358 * @return bool|string The log file path or false if path cannot be determined.
359 */
360 public static function get_log_file_path( $handle ) {
361 if ( function_exists( 'wp_hash' ) ) {
362 return trailingslashit( EVF_LOG_DIR ) . self::get_log_file_name( $handle );
363 } else {
364 evf_doing_it_wrong( __METHOD__, __( 'This method should not be called before plugins_loaded.', 'everest-forms' ), '1.2' );
365 return false;
366 }
367 }
368
369 /**
370 * Get a log file name.
371 *
372 * File names consist of the handle, followed by the date, followed by a hash, .log.
373 *
374 * @since 3.3
375 * @param string $handle Log name.
376 * @return bool|string The log file name or false if cannot be determined.
377 */
378 public static function get_log_file_name( $handle ) {
379 if ( function_exists( 'wp_hash' ) ) {
380 $date_suffix = date( 'Y-m-d', time() ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
381 $hash_suffix = wp_hash( $handle );
382 return sanitize_file_name( implode( '-', array( $handle, $date_suffix, $hash_suffix ) ) . '.log' );
383 } else {
384 evf_doing_it_wrong( __METHOD__, esc_html__( 'This method should not be called before plugins_loaded.', 'everest-forms' ), '1.2' );
385 return false;
386 }
387 }
388
389 /**
390 * Cache log to write later.
391 *
392 * @param string $entry Log entry text.
393 * @param string $handle Log entry handle.
394 */
395 protected function cache_log( $entry, $handle ) {
396 $this->cached_logs[] = array(
397 'entry' => $entry,
398 'handle' => $handle,
399 );
400 }
401
402 /**
403 * Write cached logs.
404 */
405 public function write_cached_logs() {
406 foreach ( $this->cached_logs as $log ) {
407 $this->add( $log['entry'], $log['handle'] );
408 }
409 }
410
411 /**
412 * Delete all logs older than a defined timestamp.
413 *
414 * @since 1.6.2
415 * @param integer $timestamp Timestamp to delete logs before.
416 */
417 public static function delete_logs_before_timestamp( $timestamp = 0 ) {
418 if ( ! $timestamp ) {
419 return;
420 }
421
422 $log_files = self::get_log_files();
423
424 foreach ( $log_files as $log_file ) {
425 $last_modified = filemtime( trailingslashit( EVF_LOG_DIR ) . $log_file );
426
427 if ( $last_modified < $timestamp ) {
428 @unlink( trailingslashit( EVF_LOG_DIR ) . $log_file ); // @codingStandardsIgnoreLine.
429 }
430 }
431 }
432
433 /**
434 * Get all log files in the log directory.
435 *
436 * @since 1.6.2
437 * @return array
438 */
439 public static function get_log_files() {
440 $files = @scandir( EVF_LOG_DIR ); // @codingStandardsIgnoreLine.
441 $result = array();
442
443 if ( ! empty( $files ) ) {
444 foreach ( $files as $key => $value ) {
445 if ( ! in_array( $value, array( '.', '..' ), true ) ) {
446 if ( ! is_dir( $value ) && strstr( $value, '.log' ) ) {
447 $result[ sanitize_title( $value ) ] = $value;
448 }
449 }
450 }
451 }
452
453 return $result;
454 }
455 }
456