PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.1
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.1
3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 2.8.17 2.8.18 All 259 releases
wpfunnels / includes / core / logger / class-wpfnl-logger.php

class-wpfnl-logger.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.13.1, at includes/core/logger/class-wpfnl-logger.php

376 lines 9.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Logger
4 *
5 * @package
6 */
7 use WPFunnels\Traits\SingletonTrait;
8
9 class Wpfnl_Logger {
10
11 use SingletonTrait;
12
13 /**
14 * Bumped whenever the directory hardening below changes, so an install
15 * hardened by an earlier release re-runs it once on upgrade.
16 *
17 * @since 3.12.14
18 */
19 const PROTECTION_VERSION = '2';
20
21 /**
22 * Option holding the hardening version already applied.
23 *
24 * @since 3.12.14
25 */
26 const PROTECTION_OPTION = '_wpfnl_log_protection_version';
27
28
29
30 /**
31 * Log location, URL path.
32 *
33 * @since 2.5.9
34 * @var string
35 */
36 protected $log_location_url = '';
37
38 /**
39 * Log location, server path.
40 *
41 * @since 2.5.9
42 * @var string
43 */
44 protected $log_location_file = '';
45
46 /**
47 * Log directory location, server path.
48 *
49 * @since 2.5.9
50 * @var string
51 */
52 protected $log_location_dir = '';
53
54 /**
55 * The location of the log folder's index file.
56 *
57 * @since 2.5.9
58 * @var string
59 */
60 protected $log_index_file = '';
61
62 /**
63 * The logging directory name.
64 *
65 * @since 2.5.9
66 * @var string
67 */
68 protected $log_file_dir = 'wpfunnels/wpfunnels-logs';
69
70
71 /**
72 * Constructor
73 *
74 * @return void
75 * @since 2.5.9
76 */
77 public function __construct() {
78
79 $uploads_dir = wp_upload_dir();
80 $log_file_name = 'wpfunnels';
81 $this->log_location_url = "{$uploads_dir['baseurl']}/{$this->log_file_dir}/{$log_file_name}";
82 $this->log_location_dir = "{$uploads_dir['basedir']}/{$this->log_file_dir}";
83 $this->log_location_file = "{$this->log_location_dir}/{$log_file_name}";
84 $this->log_index_file = "{$this->log_location_dir}/index.php";
85
86 define('WPFNL_LOG_FILE_DIR', $this->log_location_dir );
87 define('WPFNL_LOG_FILE', $this->log_location_file);
88 }
89
90
91 /**
92 * Create the log folder.
93 *
94 * @since 2.5.9
95 */
96 public function create_log_folder() {
97 wp_mkdir_p( $this->log_location_dir );
98 $this->protect_log_folder();
99 }
100
101
102 /**
103 * Harden the log directory.
104 *
105 * Server config files cannot carry this on their own: nginx never reads
106 * .htaccess, and neither does Apache with AllowOverride None, so on those
107 * hosts a log file under uploads stays fetchable no matter what is written
108 * beside it. The unguessable file name suffix is therefore the defence that
109 * holds everywhere, and the rules written here are the second layer for
110 * servers that do honour them.
111 *
112 * The legacy directory used up to 3.0.5 is hardened too - installs that
113 * upgraded through that release still have readable logs sitting there.
114 *
115 * @since 3.12.8
116 */
117 protected function protect_log_folder() {
118 // Runs on every page load, so the settled state must cost nothing beyond
119 // one autoloaded option read.
120 if ( get_option( self::PROTECTION_OPTION ) === self::PROTECTION_VERSION ) {
121 return;
122 }
123
124 $uploads = wp_upload_dir( null, false );
125 $legacy = ! empty( $uploads['basedir'] ) ? $uploads['basedir'] . '/wpfunnels-logs' : '';
126
127 foreach ( array( $this->log_location_dir, $legacy ) as $dir ) {
128 if ( ! $dir || ! is_dir( $dir ) || ! is_writable( $dir ) ) {
129 continue;
130 }
131
132 $this->write_protection_files( $dir, true );
133 $this->rename_predictable_logs( $dir );
134 }
135
136 update_option( self::PROTECTION_OPTION, self::PROTECTION_VERSION );
137 }
138
139
140 /**
141 * Write the deny rules and placeholders into a log directory.
142 *
143 * @param String $dir Directory to protect.
144 * @param Bool $refresh Rewrite files that already exist.
145 *
146 * @since 3.12.14
147 */
148 protected function write_protection_files( $dir, $refresh = false ) {
149 $dir = trailingslashit( $dir );
150
151 // Order/Deny is Apache 2.2 syntax. On a 2.4 build without
152 // mod_access_compat it is an unknown directive, which fails the whole
153 // directory with a 500 instead of denying anything - hence the guards.
154 $htaccess = $dir . '.htaccess';
155 if ( $refresh || ! file_exists( $htaccess ) ) {
156 $rules = "# WPFunnels log files. Deny direct web access.\n";
157 $rules .= "<IfModule mod_authz_core.c>\n";
158 $rules .= "\tRequire all denied\n";
159 $rules .= "</IfModule>\n";
160 $rules .= "<IfModule !mod_authz_core.c>\n";
161 $rules .= "\tOrder deny,allow\n";
162 $rules .= "\tDeny from all\n";
163 $rules .= "</IfModule>\n";
164 $rules .= "Options -Indexes\n";
165
166 @file_put_contents( $htaccess, $rules ); // @codingStandardsIgnoreLine.
167 }
168
169 $index = $dir . 'index.php';
170 if ( ! file_exists( $index ) ) {
171 @file_put_contents( $index, "<?php\n// Silence is golden.\n" ); // @codingStandardsIgnoreLine.
172 }
173
174 $web_config = $dir . 'web.config';
175 if ( $refresh || ! file_exists( $web_config ) ) {
176 $config = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
177 $config .= "<configuration>\n";
178 $config .= "\t<system.webServer>\n";
179 $config .= "\t\t<security>\n";
180 $config .= "\t\t\t<requestFiltering>\n";
181 $config .= "\t\t\t\t<fileExtensions>\n";
182 $config .= "\t\t\t\t\t<add fileExtension=\".log\" allowed=\"false\" />\n";
183 $config .= "\t\t\t\t</fileExtensions>\n";
184 $config .= "\t\t\t</requestFiltering>\n";
185 $config .= "\t\t</security>\n";
186 $config .= "\t</system.webServer>\n";
187 $config .= "</configuration>\n";
188
189 @file_put_contents( $web_config, $config ); // @codingStandardsIgnoreLine.
190 }
191 }
192
193
194 /**
195 * Rename log files still carrying a guessable name.
196 *
197 * Files written before 3.12.14 are named from a fixed prefix and the date
198 * alone, so hardening new writes is not enough - the existing files stay
199 * fetchable. Renaming keeps the contents and closes the exposure.
200 *
201 * @param String $dir Directory to migrate.
202 *
203 * @since 3.12.14
204 */
205 protected function rename_predictable_logs( $dir ) {
206 $suffix = self::get_log_file_suffix();
207 $files = glob( trailingslashit( $dir ) . '*.log' );
208
209 if ( ! is_array( $files ) ) {
210 return;
211 }
212
213 foreach ( $files as $file ) {
214 if ( false !== strpos( basename( $file ), $suffix ) ) {
215 continue;
216 }
217
218 $target = substr( $file, 0, -4 ) . '-' . $suffix . '.log';
219 if ( ! file_exists( $target ) ) {
220 @rename( $file, $target ); // @codingStandardsIgnoreLine.
221 }
222 }
223 }
224
225
226 /**
227 * Site-specific suffix for log file names.
228 *
229 * Derived from the site salts, so it is stable for a site and unguessable
230 * from outside it. This is what keeps the logs private on servers that
231 * ignore .htaccess.
232 *
233 * @return String
234 * @since 3.12.14
235 */
236 public static function get_log_file_suffix() {
237 return wp_hash( 'wpfnl-log-file-name' );
238 }
239
240
241 /**
242 * Create log file.
243 *
244 * @param String $log_location_dir
245 * @param String $file_name
246 *
247 * @since 2.5.9
248 * @return void
249 */
250 public static function create_log_file( $log_location_dir, $file_name ) {
251 if ( ! is_writable( $log_location_dir ) ) {
252 return;
253 }
254
255 if ( file_exists( $file_name ) ) {
256 return;
257 }
258
259 touch( $file_name );
260 }
261
262
263 /**
264 * Retrieve logging file location.
265 *
266 * @return string Logging file location.
267 *
268 * @since 2.5.9
269 */
270 public function get_logging_location() {
271 return $this->log_location_file;
272 }
273
274
275
276
277 /**
278 * Initialize Logging directory
279 *
280 * @since 2.5.9
281 */
282 public function initialize_logging() {
283 $this->create_log_folder();
284 }
285
286
287 /**
288 * Update log file
289 *
290 * @param String $log_type
291 * @param Mix $content
292 * @param String $header_text
293 *
294 * @since 2.5.9
295 */
296 public static function modify_log_file( $log_type, $content, $header_text = '' ){
297
298 if( $log_type && $content && defined( 'WPFNL_LOG_FILE' ) && defined( 'WPFNL_LOG_FILE_DIR' ) ){
299
300 $file_name = WPFNL_LOG_FILE.'-'.trim(strtolower($log_type)).'-log-'.date('Y-m-d').'-'.self::get_log_file_suffix().'.log';
301 $time = new \DateTimeImmutable('now', wp_timezone());
302 $current_time = $time->format("h:i A");
303 $header_content = "\nWPFunnels - ".$current_time." - ".$header_text."\n";
304
305 if ( !file_exists( $file_name ) ) {
306 self::create_log_file( WPFNL_LOG_FILE_DIR, $file_name );
307 }
308
309 if ( ! is_writable( $file_name ) ) {
310 return;
311 }
312
313 // Neutralise any PHP open tags in attacker-controlled content so the
314 // log file can never be executed if it is ever included by PHP.
315 $header_content = self::sanitize_log_content( $header_content );
316 $content = is_scalar( $content ) ? self::sanitize_log_content( (string) $content ) : '';
317
318 file_put_contents ($file_name, $header_content, FILE_APPEND );
319 file_put_contents ($file_name, $content, FILE_APPEND );
320
321 }
322 }
323
324
325 /**
326 * Strip PHP open tags from log content to prevent code execution.
327 *
328 * @param String $content
329 *
330 * @return String
331 * @since 3.12.8
332 */
333 protected static function sanitize_log_content( $content ) {
334 return str_ireplace( array( '<?php', '<?=', '<?' ), '', $content );
335 }
336
337
338 /**
339 * Delete existing log files.
340 *
341 * @since 2.5.9
342 *
343 * @return Bool
344 */
345 public static function delete_log_file( $file_name ) {
346 if ( file_exists( $file_name ) ) {
347 unlink( $file_name );
348 return true;
349 }
350 return false;
351 }
352
353
354 /**
355 * Get all log files in the log directory.
356 *
357 * @since 2.5.9
358 * @return array
359 */
360 public static function get_log_files() {
361 $files = @scandir( WPFNL_LOG_FILE_DIR ); // @codingStandardsIgnoreLine.
362 $result = array();
363 if ( ! empty( $files ) ) {
364 foreach ( $files as $key => $value ) {
365 if ( ! in_array( $value, array( '.', '..' ), true ) ) {
366 if ( ! is_dir( $value ) && strstr( $value, '.log' ) ) {
367 $result[ sanitize_title( $value ) ] = $value;
368 }
369 }
370 }
371 }
372
373 return $result;
374 }
375 }
376