PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.2.0
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.2.0
3.3.0 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 trunk 1.0.4 1.1.0 1.1.0.1 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 All 162 releases
cartflows / classes / logger / class-cartflows-log-handler-file.php

class-cartflows-log-handler-file.php in CartFlows – Funnel Builder & Checkout Plugin for WooCommerce 3.2.0, at classes/logger/class-cartflows-log-handler-file.php

447 lines 11.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class Cartflows_Log_Handler_File file.
4 *
5 * @package WooCommerce\Log Handlers
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit; // Exit if accessed directly.
10 }
11
12 /**
13 * Handles log entries by writing to a file.
14 *
15 * @class Cartflows_Log_Handler_File
16 * @version 1.0.0
17 * @package WooCommerce/Classes/Log_Handlers
18 */
19 class Cartflows_Log_Handler_File extends Cartflows_Log_Handler {
20
21 /**
22 * Stores open file handles.
23 *
24 * @var array
25 */
26 protected $handles = array();
27
28 /**
29 * File size limit for log files in bytes.
30 *
31 * @var int
32 */
33 protected $log_size_limit;
34
35 /**
36 * Cache logs that could not be written.
37 *
38 * If a log is written too early in the request, pluggable functions may be unavailable. These
39 * logs will be cached and written on 'plugins_loaded' action.
40 *
41 * @var array
42 */
43 protected $cached_logs = array();
44
45 /**
46 * Constructor for the logger.
47 *
48 * @param int $log_size_limit Optional. Size limit for log files. Default 5mb.
49 */
50 public function __construct( $log_size_limit = null ) {
51 if ( null === $log_size_limit ) {
52 $log_size_limit = 5 * 1024 * 1024;
53 }
54
55 $this->log_size_limit = apply_filters( 'cartflows_log_file_size_limit', $log_size_limit );
56
57 add_action( 'plugins_loaded', array( $this, 'write_cached_logs' ) );
58 }
59
60 /**
61 * Destructor.
62 *
63 * Cleans up open file handles.
64 */
65 public function __destruct() {
66 foreach ( $this->handles as $handle ) {
67 if ( is_resource( $handle ) ) {
68 fclose( $handle ); // @codingStandardsIgnoreLine.
69 }
70 }
71 }
72
73 /**
74 * Handle a log entry.
75 *
76 * @param int $timestamp Log timestamp.
77 * @param string $level emergency|alert|critical|error|warning|notice|info|debug.
78 * @param string $message Log message.
79 * @param array $context {
80 * Additional information for log handlers.
81 *
82 * @type string $source Optional. Determines log file to write to. Default 'log'.
83 * @type bool $_legacy Optional. Default false. True to use outdated log format
84 * originally used in deprecated Cartflows_WC_Logger::add calls.
85 * }
86 *
87 * @return bool False if value was not handled and true if value was handled.
88 */
89 public function handle( $timestamp, $level, $message, $context ) {
90
91 if ( isset( $context['source'] ) && $context['source'] ) {
92 $handle = $context['source'];
93 } else {
94 $handle = 'log';
95 }
96
97 $entry = self::format_entry( $timestamp, $level, $message, $context );
98
99 return $this->add( $entry, $handle );
100 }
101
102 /**
103 * Builds a log entry text from timestamp, level and message.
104 *
105 * @param int $timestamp Log timestamp.
106 * @param string $level emergency|alert|critical|error|warning|notice|info|debug.
107 * @param string $message Log message.
108 * @param array $context Additional information for log handlers.
109 *
110 * @return string Formatted log entry.
111 */
112 protected static function format_entry( $timestamp, $level, $message, $context ) {
113
114 if ( isset( $context['_legacy'] ) && true === $context['_legacy'] ) {
115 if ( isset( $context['source'] ) && $context['source'] ) {
116 $handle = $context['source'];
117 } else {
118 $handle = 'log';
119 }
120 $message = apply_filters( 'cartflows_logger_add_message', $message, $handle );
121 $time = date_i18n( 'm-d-Y @ H:i:s' );
122 $entry = "{$time} - {$message}";
123 } else {
124 $entry = parent::format_entry( $timestamp, $level, $message, $context );
125 }
126
127 return $entry;
128 }
129
130 /**
131 * Open log file for writing.
132 *
133 * @param string $handle Log handle.
134 * @param string $mode Optional. File mode. Default 'a'.
135 * @return bool Success.
136 */
137 protected function open( $handle, $mode = 'a' ) {
138 if ( $this->is_open( $handle ) ) {
139 return true;
140 }
141
142 $file = self::get_log_file_path( $handle );
143
144 if ( $file ) {
145 if ( ! file_exists( $file ) ) {
146 $temphandle = @fopen( $file, 'w+' ); // @codingStandardsIgnoreLine.
147
148 if ( is_resource( $temphandle ) ) {
149
150 @fclose( $temphandle ); // @codingStandardsIgnoreLine.
151
152 if ( defined( 'FS_CHMOD_FILE' ) ) {
153 @chmod( $file, FS_CHMOD_FILE ); // @codingStandardsIgnoreLine.
154 }
155 }
156 }
157
158 $resource = @fopen( $file, $mode ); // @codingStandardsIgnoreLine.
159
160 if ( $resource ) {
161 $this->handles[ $handle ] = $resource;
162 return true;
163 }
164 }
165
166 return false;
167 }
168
169 /**
170 * Check if a handle is open.
171 *
172 * @param string $handle Log handle.
173 * @return bool True if $handle is open.
174 */
175 protected function is_open( $handle ) {
176 return array_key_exists( $handle, $this->handles ) && is_resource( $this->handles[ $handle ] );
177 }
178
179 /**
180 * Close a handle.
181 *
182 * @param string $handle Log handle.
183 * @return bool success
184 */
185 protected function close( $handle ) {
186 $result = false;
187
188 if ( $this->is_open( $handle ) ) {
189 $result = fclose( $this->handles[ $handle ] ); // @codingStandardsIgnoreLine.
190 unset( $this->handles[ $handle ] );
191 }
192
193 return $result;
194 }
195
196 /**
197 * Add a log entry to chosen file.
198 *
199 * @param string $entry Log entry text.
200 * @param string $handle Log entry handle.
201 *
202 * @return bool True if write was successful.
203 */
204 protected function add( $entry, $handle ) {
205 $result = false;
206
207 if ( $this->should_rotate( $handle ) ) {
208 $this->log_rotate( $handle );
209 }
210
211 if ( $this->open( $handle ) && is_resource( $this->handles[ $handle ] ) ) {
212 $result = fwrite( $this->handles[ $handle ], $entry . PHP_EOL ); // @codingStandardsIgnoreLine.
213 } else {
214 $this->cache_log( $entry, $handle );
215 }
216
217 return false !== $result;
218 }
219
220 /**
221 * Clear entries from chosen file.
222 *
223 * @param string $handle Log handle.
224 *
225 * @return bool
226 */
227 public function clear( $handle ) {
228 $result = false;
229
230 // Close the file if it's already open.
231 $this->close( $handle );
232
233 /**
234 * $this->open( $handle, 'w' ) == Open the file for writing only. Place the file pointer at
235 * the beginning of the file, and truncate the file to zero length.
236 */
237 if ( $this->open( $handle, 'w' ) && is_resource( $this->handles[ $handle ] ) ) {
238 $result = true;
239 }
240
241 do_action( 'cartflows_log_clear', $handle );
242
243 return $result;
244 }
245
246 /**
247 * Remove/delete the chosen file.
248 *
249 * @param string $handle Log handle.
250 *
251 * @return bool
252 */
253 public function remove( $handle ) {
254 $removed = false;
255 $logs = $this->get_log_files();
256 $handle = sanitize_title( $handle );
257
258 if ( isset( $logs[ $handle ] ) && $logs[ $handle ] ) {
259 $file = realpath( trailingslashit( CARTFLOWS_LOG_DIR ) . $logs[ $handle ] );
260 if ( 0 === stripos( $file, realpath( trailingslashit( CARTFLOWS_LOG_DIR ) ) ) && is_file( $file ) && is_writable( $file ) ) { // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_is_writable
261 $this->close( $file ); // Close first to be certain no processes keep it alive after it is unlinked.
262 $removed = unlink( $file ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_unlink
263 }
264 do_action( 'cartflows_log_remove', $handle, $removed );
265 }
266 return $removed;
267 }
268
269 /**
270 * Check if log file should be rotated.
271 *
272 * Compares the size of the log file to determine whether it is over the size limit.
273 *
274 * @param string $handle Log handle.
275 * @return bool True if if should be rotated.
276 */
277 protected function should_rotate( $handle ) {
278 $file = self::get_log_file_path( $handle );
279 if ( $file ) {
280 if ( $this->is_open( $handle ) ) {
281 $file_stat = fstat( $this->handles[ $handle ] );
282 return $file_stat['size'] > $this->log_size_limit;
283 } elseif ( file_exists( $file ) ) {
284 return filesize( $file ) > $this->log_size_limit;
285 } else {
286 return false;
287 }
288 } else {
289 return false;
290 }
291 }
292
293 /**
294 * Rotate log files.
295 *
296 * Logs are rotated by prepending '.x' to the '.log' suffix.
297 * The current log plus 10 historical logs are maintained.
298 * For example:
299 * base.9.log -> [ REMOVED ]
300 * base.8.log -> base.9.log
301 * ...
302 * base.0.log -> base.1.log
303 * base.log -> base.0.log
304 *
305 * @param string $handle Log handle.
306 */
307 protected function log_rotate( $handle ) {
308 for ( $i = 8; $i >= 0; $i-- ) {
309 $this->increment_log_infix( $handle, $i );
310 }
311 $this->increment_log_infix( $handle );
312 }
313
314 /**
315 * Increment a log file suffix.
316 *
317 * @param string $handle Log handle.
318 * @param null|int $number Optional. Default null. Log suffix number to be incremented.
319 * @return bool True if increment was successful, otherwise false.
320 */
321 protected function increment_log_infix( $handle, $number = null ) {
322 if ( null === $number ) {
323 $suffix = '';
324 $next_suffix = '.0';
325 } else {
326 $suffix = '.' . $number;
327 $next_suffix = '.' . ( $number + 1 );
328 }
329
330 $rename_from = self::get_log_file_path( "{$handle}{$suffix}" );
331 $rename_to = self::get_log_file_path( "{$handle}{$next_suffix}" );
332
333 if ( $this->is_open( $rename_from ) ) {
334 $this->close( $rename_from );
335 }
336
337 if ( is_writable( $rename_from ) ) { //phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_is_writable
338 return rename( $rename_from, $rename_to ); //phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_rename
339 } else {
340 return false;
341 }
342
343 }
344
345 /**
346 * Get a log file path.
347 *
348 * @param string $handle Log name.
349 * @return bool|string The log file path or false if path cannot be determined.
350 */
351 public static function get_log_file_path( $handle ) {
352 if ( function_exists( 'wp_hash' ) ) {
353 return trailingslashit( CARTFLOWS_LOG_DIR ) . self::get_log_file_name( $handle );
354 } else {
355 wc_doing_it_wrong( __METHOD__, __( 'This method should not be called before plugins_loaded.', 'cartflows' ), '3.0' );
356 return false;
357 }
358 }
359
360 /**
361 * Get a log file name.
362 *
363 * File names consist of the handle, followed by the date, followed by a hash, .log.
364 *
365 * @since 3.3
366 * @param string $handle Log name.
367 * @return bool|string The log file name or false if cannot be determined.
368 */
369 public static function get_log_file_name( $handle ) {
370 if ( function_exists( 'wp_hash' ) ) {
371 $date_suffix = gmdate( 'Y-m-d', time() );
372 $hash_suffix = wp_hash( $handle );
373 return sanitize_file_name( implode( '-', array( $handle, $date_suffix, $hash_suffix ) ) . '.log' );
374 } else {
375 wc_doing_it_wrong( __METHOD__, __( 'This method should not be called before plugins_loaded.', 'cartflows' ), '3.3' );
376 return false;
377 }
378 }
379
380 /**
381 * Cache log to write later.
382 *
383 * @param string $entry Log entry text.
384 * @param string $handle Log entry handle.
385 */
386 protected function cache_log( $entry, $handle ) {
387 $this->cached_logs[] = array(
388 'entry' => $entry,
389 'handle' => $handle,
390 );
391 }
392
393 /**
394 * Write cached logs.
395 */
396 public function write_cached_logs() {
397 foreach ( $this->cached_logs as $log ) {
398 $this->add( $log['entry'], $log['handle'] );
399 }
400 }
401
402 /**
403 * Delete all logs older than a defined timestamp.
404 *
405 * @since 3.4.0
406 * @param integer $timestamp Timestamp to delete logs before.
407 */
408 public static function delete_logs_before_timestamp( $timestamp = 0 ) {
409 if ( ! $timestamp ) {
410 return;
411 }
412
413 $log_files = self::get_log_files();
414
415 foreach ( $log_files as $log_file ) {
416 $last_modified = filemtime( trailingslashit( CARTFLOWS_LOG_DIR ) . $log_file );
417
418 if ( $last_modified < $timestamp ) {
419 @unlink( trailingslashit( CARTFLOWS_LOG_DIR ) . $log_file ); // @codingStandardsIgnoreLine.
420 }
421 }
422 }
423
424 /**
425 * Get all log files in the log directory.
426 *
427 * @since 3.4.0
428 * @return array
429 */
430 public static function get_log_files() {
431 $files = @scandir( CARTFLOWS_LOG_DIR ); // @codingStandardsIgnoreLine.
432 $result = array();
433
434 if ( ! empty( $files ) ) {
435 foreach ( $files as $key => $value ) {
436 if ( ! in_array( $value, array( '.', '..' ), true ) ) {
437 if ( ! is_dir( $value ) && strstr( $value, '.log' ) ) {
438 $result[ sanitize_title( $value ) ] = $value;
439 }
440 }
441 }
442 }
443
444 return $result;
445 }
446 }
447