PluginProbe ʕ •ᴥ•ʔ
TinyPNG – JPEG, PNG & WebP image compression / 3.8.0
TinyPNG – JPEG, PNG & WebP image compression v3.8.0
3.8.0 3.7.0 3.6.14 trunk 1.0.0 1.1.0 1.2.0 1.2.1 1.3.0 1.3.1 1.3.2 1.4.0 1.5.0 1.6.0 1.7.0 1.7.1 1.7.2 2.0.0 2.0.1 2.0.2 2.1.0 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 3.0.0 3.0.1 3.1.0 3.2.0 3.2.1 3.3 3.4 3.4.1 3.4.2 3.4.4 3.4.5 3.4.6 3.5.0 3.5.1 3.5.2 3.6.0 3.6.1 3.6.10 3.6.11 3.6.12 3.6.13 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.6.9
tiny-compress-images / src / class-tiny-logger.php
tiny-compress-images / src Last commit date
compatibility 2 months ago config 2 months ago css 7 months ago data 4 years ago images 4 years ago js 1 week ago vendor 6 months ago views 1 week ago class-tiny-apache-rewrite.php 2 months ago class-tiny-bulk-optimization.php 2 months ago class-tiny-cli.php 2 months ago class-tiny-compress-client.php 1 week ago class-tiny-compress-fopen.php 2 months ago class-tiny-compress.php 1 week ago class-tiny-conversion.php 4 months ago class-tiny-diagnostics.php 7 months ago class-tiny-exception.php 7 months ago class-tiny-helpers.php 2 months ago class-tiny-image-size.php 1 week ago class-tiny-image.php 1 week ago class-tiny-logger.php 2 months ago class-tiny-migrate.php 2 months ago class-tiny-notices.php 2 months ago class-tiny-php.php 2 months ago class-tiny-picture.php 2 months ago class-tiny-plugin.php 1 week ago class-tiny-settings.php 1 week ago class-tiny-source-base.php 1 week ago class-tiny-source-image.php 7 months ago class-tiny-source-picture.php 7 months ago class-tiny-wp-base.php 2 months ago
class-tiny-logger.php
271 lines
1 <?php
2 /*
3 * Tiny Compress Images - WordPress plugin.
4 * Copyright (C) 2015-2026 Tinify B.V.
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the Free
8 * Software Foundation; either version 2 of the License, or (at your option)
9 * any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 * more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc., 51
18 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21
22 /**
23 * Handles logging of plugin events to file.
24 *
25 * @since 3.7.0
26 */
27 class Tiny_Logger {
28
29
30 const LOG_LEVEL_ERROR = 'error';
31 const LOG_LEVEL_DEBUG = 'debug';
32
33 const MAX_LOG_SIZE = 2 * 1024 * 1024; // 2MB
34
35 private static $instance = null;
36
37 private $log_enabled = null;
38 private $log_file_path = null;
39
40 /**
41 * To log on various places easily, we create a singleton
42 * to prevent passing around the instance.
43 *
44 * @return Tiny_Logger The logger instance.
45 */
46 public static function get_instance() {
47 if ( null === self::$instance ) {
48 self::$instance = new self();
49 }
50 return self::$instance;
51 }
52
53 /**
54 * Constructor.
55 * sets log_file path and log_enabled
56 */
57 private function __construct() {
58 $this->log_file_path = $this->resolve_log_file_path();
59 $this->log_enabled = 'on' === get_option( 'tinypng_logging_enabled', false );
60 }
61
62 /**
63 * Initializes the logger by registering WordPress hooks.
64 *
65 * This method hooks into 'pre_update_option_tinypng_logging_enabled' to
66 * intercept and process logging settings before they are saved to the database.
67 *
68 * @return void
69 */
70 public static function init() {
71 add_filter(
72 'pre_update_option_tinypng_logging_enabled',
73 'Tiny_Logger::on_save_log_enabled',
74 10,
75 2
76 );
77 }
78
79 /**
80 * Resets the singleton instance.
81 * Used primarily for unit testing.
82 *
83 * @return void
84 */
85 public static function reset() {
86 self::$instance = null;
87 }
88
89 /**
90 * Retrieves whether logging is currently enabled.
91 *
92 * @return bool True if logging is enabled, false otherwise.
93 */
94 public function get_log_enabled() {
95 return $this->log_enabled;
96 }
97
98 /**
99 * Retrieves the absolute filesystem path to the log file.
100 *
101 * @return string The full filesystem path to the tiny-compress.log file.
102 */
103 public function get_log_file_path() {
104 return $this->log_file_path;
105 }
106
107 /**
108 * Triggered when log_enabled is saved
109 * - set the setting on the instance
110 * - will clear logs when turned on
111 *
112 * Hooked to `pre_update_option_tinypng_logging_enabled` filter
113 *
114 * @return bool true if enabled
115 */
116 public static function on_save_log_enabled( $log_enabled, $old ) {
117 $instance = self::get_instance();
118 $was_enabled = 'on' === $old;
119 $is_now_enabled = 'on' === $log_enabled;
120 $instance->log_enabled = $is_now_enabled;
121
122 if ( ! $was_enabled && $is_now_enabled ) {
123 self::clear_logs();
124 }
125
126 return $log_enabled;
127 }
128
129 /**
130 * Retrieves the log path using wp_upload_dir. This operation
131 * should only be used internally. Use the getter to get the
132 * memoized function.
133 *
134 * @return string The log file path.
135 */
136 private function resolve_log_file_path() {
137 $upload_dir = wp_upload_dir();
138 $log_dir = trailingslashit( $upload_dir['basedir'] ) . 'tiny-compress-logs';
139 return trailingslashit( $log_dir ) . 'tiny-compress.log';
140 }
141
142 /**
143 * Logs an error message.
144 *
145 * @param string $message The message to log.
146 * @param array $context Optional. Additional context data. Default empty array.
147 */
148 public static function error( $message, $context = array() ) {
149 $instance = self::get_instance();
150 $instance->log( self::LOG_LEVEL_ERROR, $message, $context );
151 }
152
153 /**
154 * Logs a debug message.
155 *
156 * @param string $message The message to log.
157 * @param array $context Optional. Additional context data. Default empty array.
158 */
159 public static function debug( $message, $context = array() ) {
160 $instance = self::get_instance();
161 $instance->log( self::LOG_LEVEL_DEBUG, $message, $context );
162 }
163
164 /**
165 * Logs a message.
166 *
167 * @param string $level The log level.
168 * @param string $message The message to log.
169 * @param array $context Optional. Additional context data. Default empty array.
170 * @return void
171 */
172 private function log( $level, $message, $context = array() ) {
173 if ( ! $this->log_enabled ) {
174 return;
175 }
176
177 $this->rotate_logs();
178
179 // Ensure log directory exists.
180 $log_dir = dirname( $this->log_file_path );
181 $wp_filesystem = Tiny_Helpers::get_wp_filesystem();
182 if ( ! $wp_filesystem->exists( $log_dir ) ) {
183 wp_mkdir_p( $log_dir );
184 self::create_blocking_files( $log_dir );
185 }
186
187 $timestamp = current_time( 'Y-m-d H:i:s' );
188 $level_str = strtoupper( $level );
189 $context_str = ! empty( $context ) ? ' ' . wp_json_encode( $context ) : '';
190 $log_entry = "[{$timestamp}] [{$level_str}] {$message}{$context_str}" . PHP_EOL;
191
192 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
193 error_log( $log_entry, 3, $this->log_file_path );
194 }
195
196 /**
197 * Deletes log file and creates a new one when the
198 * MAX_LOG_SIZE is met.
199 *
200 * @return void
201 */
202 private function rotate_logs() {
203 $wp_filesystem = Tiny_Helpers::get_wp_filesystem();
204 if ( ! $wp_filesystem->exists( $this->log_file_path ) ) {
205 return;
206 }
207
208 $file_size = $wp_filesystem->size( $this->log_file_path );
209 if ( $file_size < self::MAX_LOG_SIZE ) {
210 return;
211 }
212
213 $wp_filesystem->delete( $this->log_file_path );
214 }
215
216 /**
217 * Clears log file
218 *
219 * @return bool True if logs were cleared successfully.
220 */
221 public static function clear_logs() {
222 $instance = self::get_instance();
223 $log_path = $instance->get_log_file_path();
224 $wp_filesystem = Tiny_Helpers::get_wp_filesystem();
225 $file_exits = $wp_filesystem->exists( $log_path );
226 if ( $file_exits ) {
227 return $wp_filesystem->delete( $log_path );
228 }
229
230 return true;
231 }
232
233 /**
234 * Creates defensive files to prevent direct access to log directory.
235 * Adds index.html to prevent directory listing and .htaccess to block access.
236 *
237 * @param string $log_dir The path to the log directory.
238 * @return void
239 */
240 private static function create_blocking_files( $log_dir ) {
241 $wp_filesystem = Tiny_Helpers::get_wp_filesystem();
242
243 $index_file = trailingslashit( $log_dir ) . 'index.html';
244 if ( ! $wp_filesystem->exists( $index_file ) ) {
245 $index_content = '<!-- Silence is golden -->';
246 $wp_filesystem->put_contents( $index_file, $index_content, FS_CHMOD_FILE );
247 }
248
249 $htaccess_file = trailingslashit( $log_dir ) . '.htaccess';
250 if ( ! $wp_filesystem->exists( $htaccess_file ) ) {
251 $htaccess_content = 'deny from all';
252 $wp_filesystem->put_contents( $htaccess_file, $htaccess_content, FS_CHMOD_FILE );
253 }
254 }
255
256 /**
257 * Gets all log file paths.
258 *
259 * @return array Array of log file paths.
260 */
261 public function get_log_files() {
262 $files = array();
263
264 if ( file_exists( $this->log_file_path ) ) {
265 $files[] = $this->log_file_path;
266 }
267
268 return $files;
269 }
270 }
271