PluginProbe ʕ •ᴥ•ʔ
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI / 1.6.3
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI v1.6.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 6 years ago
class-evf-log-handler-file.php
435 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 * EVF_Log_Handler_File class.
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 * Check if log file should be rotated.
260 *
261 * Compares the size of the log file to determine whether it is over the size limit.
262 *
263 * @param string $handle Log handle.
264 * @return bool True if if should be rotated.
265 */
266 protected function should_rotate( $handle ) {
267 $file = self::get_log_file_path( $handle );
268 if ( $file ) {
269 if ( $this->is_open( $handle ) ) {
270 $file_stat = fstat( $this->handles[ $handle ] );
271 return $file_stat['size'] > $this->log_size_limit;
272 } elseif ( file_exists( $file ) ) {
273 return filesize( $file ) > $this->log_size_limit;
274 } else {
275 return false;
276 }
277 } else {
278 return false;
279 }
280 }
281
282 /**
283 * Rotate log files.
284 *
285 * Logs are rotated by prepending '.x' to the '.log' suffix.
286 * The current log plus 10 historical logs are maintained.
287 * For example:
288 * base.9.log -> [ REMOVED ]
289 * base.8.log -> base.9.log
290 * ...
291 * base.0.log -> base.1.log
292 * base.log -> base.0.log
293 *
294 * @param string $handle Log handle.
295 */
296 protected function log_rotate( $handle ) {
297 for ( $i = 8; $i >= 0; $i-- ) {
298 $this->increment_log_infix( $handle, $i );
299 }
300 $this->increment_log_infix( $handle );
301 }
302
303 /**
304 * Increment a log file suffix.
305 *
306 * @param string $handle Log handle.
307 * @param null|int $number Optional. Default null. Log suffix number to be incremented.
308 * @return bool True if increment was successful, otherwise false.
309 */
310 protected function increment_log_infix( $handle, $number = null ) {
311 if ( null === $number ) {
312 $suffix = '';
313 $next_suffix = '.0';
314 } else {
315 $suffix = '.' . $number;
316 $next_suffix = '.' . ( $number + 1 );
317 }
318
319 $rename_from = self::get_log_file_path( "{$handle}{$suffix}" );
320 $rename_to = self::get_log_file_path( "{$handle}{$next_suffix}" );
321
322 if ( $this->is_open( $rename_from ) ) {
323 $this->close( $rename_from );
324 }
325
326 if ( is_writable( $rename_from ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable
327 return rename( $rename_from, $rename_to ); // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_rename
328 } else {
329 return false;
330 }
331 }
332
333 /**
334 * Get a log file path.
335 *
336 * @param string $handle Log name.
337 * @return bool|string The log file path or false if path cannot be determined.
338 */
339 public static function get_log_file_path( $handle ) {
340 if ( function_exists( 'wp_hash' ) ) {
341 return trailingslashit( EVF_LOG_DIR ) . self::get_log_file_name( $handle );
342 } else {
343 evf_doing_it_wrong( __METHOD__, __( 'This method should not be called before plugins_loaded.', 'everest-forms' ), '1.2' );
344 return false;
345 }
346 }
347
348 /**
349 * Get a log file name.
350 *
351 * File names consist of the handle, followed by the date, followed by a hash, .log.
352 *
353 * @since 3.3
354 * @param string $handle Log name.
355 * @return bool|string The log file name or false if cannot be determined.
356 */
357 public static function get_log_file_name( $handle ) {
358 if ( function_exists( 'wp_hash' ) ) {
359 $date_suffix = date( 'Y-m-d', time() ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
360 $hash_suffix = wp_hash( $handle );
361 return sanitize_file_name( implode( '-', array( $handle, $date_suffix, $hash_suffix ) ) . '.log' );
362 } else {
363 evf_doing_it_wrong( __METHOD__, __( 'This method should not be called before plugins_loaded.', 'everest-forms' ), '1.2' );
364 return false;
365 }
366 }
367
368 /**
369 * Cache log to write later.
370 *
371 * @param string $entry Log entry text.
372 * @param string $handle Log entry handle.
373 */
374 protected function cache_log( $entry, $handle ) {
375 $this->cached_logs[] = array(
376 'entry' => $entry,
377 'handle' => $handle,
378 );
379 }
380
381 /**
382 * Write cached logs.
383 */
384 public function write_cached_logs() {
385 foreach ( $this->cached_logs as $log ) {
386 $this->add( $log['entry'], $log['handle'] );
387 }
388 }
389
390 /**
391 * Delete all logs older than a defined timestamp.
392 *
393 * @since 1.6.2
394 * @param integer $timestamp Timestamp to delete logs before.
395 */
396 public static function delete_logs_before_timestamp( $timestamp = 0 ) {
397 if ( ! $timestamp ) {
398 return;
399 }
400
401 $log_files = self::get_log_files();
402
403 foreach ( $log_files as $log_file ) {
404 $last_modified = filemtime( trailingslashit( EVF_LOG_DIR ) . $log_file );
405
406 if ( $last_modified < $timestamp ) {
407 @unlink( trailingslashit( EVF_LOG_DIR ) . $log_file ); // @codingStandardsIgnoreLine.
408 }
409 }
410 }
411
412 /**
413 * Get all log files in the log directory.
414 *
415 * @since 1.6.2
416 * @return array
417 */
418 public static function get_log_files() {
419 $files = @scandir( EVF_LOG_DIR ); // @codingStandardsIgnoreLine.
420 $result = array();
421
422 if ( ! empty( $files ) ) {
423 foreach ( $files as $key => $value ) {
424 if ( ! in_array( $value, array( '.', '..' ), true ) ) {
425 if ( ! is_dir( $value ) && strstr( $value, '.log' ) ) {
426 $result[ sanitize_title( $value ) ] = $value;
427 }
428 }
429 }
430 }
431
432 return $result;
433 }
434 }
435