PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.8.0
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.8.0
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / includes / Utils / Log / LogFile.php

LogFile.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! 3.8.0, at includes/Utils/Log/LogFile.php

180 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Templately\Utils\Log;
4
5 /**
6 * The file sink behind `Helper::log()` / `Logger` — a dedicated log file under
7 * the uploads dir instead of the shared `wp-content/debug.log`.
8 *
9 * Why not debug.log: Templately's log carries the server-side detail spec 043
10 * deliberately keeps OFF the wire (stack traces, upstream bodies, file paths).
11 * Mixing it into the site-wide debug.log buries it among every other plugin's
12 * noise and couples support diagnostics to a file the host may rotate, disable,
13 * or expose. A dedicated file is also what the dev `log-viewer` module already
14 * lists (`uploads/templately/log/*.log`).
15 *
16 * Security — the uploads dir is web-reachable, so the file must not be
17 * publicly fetchable:
18 * - the filename embeds an unguessable per-site hash derived from `wp_salt()`
19 * (the primary defense — works on every server),
20 * - the directory gets an `.htaccess` deny (Apache) and a blank `index.php`
21 * (defense-in-depth; nginx ignores `.htaccess`, hence the hash).
22 *
23 * Rotation: one previous generation is kept. When the live file exceeds
24 * MAX_BYTES it is renamed `*-old.log` (replacing any prior generation) and a
25 * fresh file starts — bounded disk use, and the tail of the previous window
26 * survives for support.
27 *
28 * If the uploads dir is unavailable/unwritable the line falls back to
29 * `error_log()` — a degraded destination beats a silently dropped log.
30 *
31 * Gate-free by design: callers own their gates (`Helper::log()` checks
32 * `WP_DEBUG_LOG`; dev-kernel checks its own feature flag). This class only
33 * answers "where do log lines go".
34 */
35 class LogFile {
36
37 /**
38 * Rotate when the live file exceeds this. 5 MB keeps weeks of typical
39 * output while bounding worst-case disk use to ~10 MB (live + old).
40 */
41 const MAX_BYTES = 5242880;
42
43 /**
44 * Memoized resolved path for this request.
45 * null = not resolved yet; false = uploads unavailable (fall back).
46 *
47 * @var string|false|null
48 */
49 private static $path = null;
50
51 /**
52 * Append one already-formatted line to the Templately log file.
53 *
54 * @param string $line Formatted log line (no trailing newline needed).
55 * @return void
56 */
57 public static function write( $line ) {
58 $path = self::path();
59
60 if ( ! $path ) {
61 error_log( $line ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- deliberate fallback when uploads is unwritable.
62 return;
63 }
64
65 self::maybe_rotate( $path );
66
67 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- append with LOCK_EX; WP_Filesystem has no locking append.
68 $written = @file_put_contents( $path, $line . PHP_EOL, FILE_APPEND | LOCK_EX );
69
70 if ( false === $written ) {
71 error_log( $line ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- deliberate fallback when the write fails.
72 }
73 }
74
75 /**
76 * Resolve (and memoize) the full path of the live log file, provisioning
77 * the directory and its guard files on first use.
78 *
79 * @return string|false Absolute path, or false when uploads is unusable.
80 */
81 public static function path() {
82 if ( null !== self::$path ) {
83 return self::$path;
84 }
85
86 $upload_dir = wp_upload_dir( null, false );
87
88 if ( ! empty( $upload_dir['error'] ) || empty( $upload_dir['basedir'] ) ) {
89 self::$path = false;
90 return self::$path;
91 }
92
93 // Matches where the dev log-viewer module already looks
94 // (uploads/templately/log/*.log) — per-site on multisite via basedir.
95 $dir = $upload_dir['basedir'] . '/templately/log';
96
97 if ( ! is_dir( $dir ) && ! wp_mkdir_p( $dir ) ) {
98 self::$path = false;
99 return self::$path;
100 }
101
102 self::ensure_guards( $dir );
103
104 self::$path = $dir . '/' . self::filename();
105 return self::$path;
106 }
107
108 /**
109 * The per-site log filename: deterministic (so every request appends to the
110 * same file) but unguessable from outside (derived from the site's salts).
111 *
112 * @return string
113 */
114 public static function filename() {
115 $hash = substr( md5( wp_salt( 'auth' ) . '|templately-log-file' ), 0, 12 );
116
117 return "templately-{$hash}.log";
118 }
119
120 /**
121 * Forget the memoized path (tests; or after switch_to_blog when a caller
122 * needs the new site's file).
123 *
124 * @return void
125 */
126 public static function reset() {
127 self::$path = null;
128 }
129
130 /**
131 * Drop the deny/index guard files into the log dir once.
132 *
133 * @param string $dir Log directory (exists).
134 * @return void
135 */
136 private static function ensure_guards( $dir ) {
137 $htaccess = $dir . '/.htaccess';
138 if ( ! file_exists( $htaccess ) ) {
139 $rules = "# Deny direct access to Templately log files.\n"
140 . "<IfModule mod_authz_core.c>\n\tRequire all denied\n</IfModule>\n"
141 . "<IfModule !mod_authz_core.c>\n\tOrder deny,allow\n\tDeny from all\n</IfModule>\n";
142 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- one-time guard file, best-effort.
143 @file_put_contents( $htaccess, $rules );
144 }
145
146 $index = $dir . '/index.php';
147 if ( ! file_exists( $index ) ) {
148 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- one-time guard file, best-effort.
149 @file_put_contents( $index, "<?php // Silence is golden.\n" );
150 }
151 }
152
153 /**
154 * Rotate the live file out to `*-old.log` when it exceeds MAX_BYTES,
155 * keeping exactly one previous generation.
156 *
157 * @param string $path Live log file path.
158 * @return void
159 */
160 private static function maybe_rotate( $path ) {
161 if ( ! file_exists( $path ) ) {
162 return;
163 }
164
165 $size = @filesize( $path );
166 if ( false === $size || $size < self::MAX_BYTES ) {
167 return;
168 }
169
170 $old = substr( $path, 0, -4 ) . '-old.log';
171
172 // Windows rename() fails onto an existing target — clear it first.
173 if ( file_exists( $old ) ) {
174 @unlink( $old );
175 }
176
177 @rename( $path, $old );
178 }
179 }
180