PluginProbe ʕ •ᴥ•ʔ
TinyPNG – JPEG, PNG & WebP image compression / 3.6.13
TinyPNG – JPEG, PNG & WebP image compression v3.6.13
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 5 months ago config 5 months ago css 5 months ago data 3 years ago images 3 years ago js 5 months ago vendor 4 months ago views 2 months ago class-tiny-apache-rewrite.php 2 months ago class-tiny-bulk-optimization.php 5 months ago class-tiny-cli.php 5 months ago class-tiny-compress-client.php 5 months ago class-tiny-compress-fopen.php 5 months ago class-tiny-compress.php 5 months ago class-tiny-conversion.php 2 months ago class-tiny-diagnostics.php 5 months ago class-tiny-exception.php 5 months ago class-tiny-helpers.php 5 months ago class-tiny-image-size.php 5 months ago class-tiny-image.php 5 months ago class-tiny-logger.php 2 months ago class-tiny-notices.php 2 months ago class-tiny-php.php 5 months ago class-tiny-picture.php 4 months ago class-tiny-plugin.php 2 months ago class-tiny-settings.php 2 months ago class-tiny-source-base.php 2 months ago class-tiny-source-image.php 5 months ago class-tiny-source-picture.php 5 months ago class-tiny-wp-base.php 5 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 *
26 * @since 3.7.0
27 */
28 class Tiny_Logger {
29
30
31 const LOG_LEVEL_ERROR = 'error';
32 const LOG_LEVEL_DEBUG = 'debug';
33
34 const MAX_LOG_SIZE = 2 * 1024 * 1024; // 2MB
35
36 private static $instance = null;
37
38 private $log_enabled = null;
39 private $log_file_path = null;
40
41 /**
42 * To log on various places easily, we create a singleton
43 * to prevent passing around the instance.
44 *
45 * @return Tiny_Logger The logger instance.
46 */
47 public static function get_instance() {
48 if ( null === self::$instance ) {
49 self::$instance = new self();
50 }
51 return self::$instance;
52 }
53
54 /**
55 * Constructor.
56 * sets log_file path and log_enabled
57 */
58 private function __construct() {
59 $this->log_file_path = $this->resolve_log_file_path();
60 $this->log_enabled = 'on' === get_option( 'tinypng_logging_enabled', false );
61 }
62
63 /**
64 * Initializes the logger by registering WordPress hooks.
65 *
66 * This method hooks into 'pre_update_option_tinypng_logging_enabled' to
67 * intercept and process logging settings before they are saved to the database.
68 *
69 * @return void
70 */
71 public static function init() {
72 add_filter(
73 'pre_update_option_tinypng_logging_enabled',
74 'Tiny_Logger::on_save_log_enabled',
75 10,
76 3
77 );
78 }
79
80 /**
81 * Resets the singleton instance.
82 * Used primarily for unit testing.
83 *
84 * @return void
85 */
86 public static function reset() {
87 self::$instance = null;
88 }
89
90 /**
91 * Retrieves whether logging is currently enabled.
92 *
93 * @return bool True if logging is enabled, false otherwise.
94 */
95 public function get_log_enabled() {
96 return $this->log_enabled;
97 }
98
99 /**
100 * Retrieves the absolute filesystem path to the log file.
101 *
102 * @return string The full filesystem path to the tiny-compress.log file.
103 */
104 public function get_log_file_path() {
105 return $this->log_file_path;
106 }
107
108 /**
109 * Triggered when log_enabled is saved
110 * - set the setting on the instance
111 * - will clear logs when turned on
112 *
113 * Hooked to `pre_update_option_tinypng_logging_enabled` filter
114 *
115 * @return bool true if enabled
116 */
117 public static function on_save_log_enabled( $log_enabled, $old, $option ) {
118 $instance = self::get_instance();
119 $was_enabled = 'on' === $old;
120 $is_now_enabled = 'on' === $log_enabled;
121 $instance->log_enabled = $is_now_enabled;
122
123 if ( ! $was_enabled && $is_now_enabled ) {
124 self::clear_logs();
125 }
126
127 return $log_enabled;
128 }
129
130 /**
131 * Retrieves the log path using wp_upload_dir. This operation
132 * should only be used internally. Use the getter to get the
133 * memoized function.
134 *
135 * @return string The log file path.
136 */
137 private function resolve_log_file_path() {
138 $upload_dir = wp_upload_dir();
139 $log_dir = trailingslashit( $upload_dir['basedir'] ) . 'tiny-compress-logs';
140 return trailingslashit( $log_dir ) . 'tiny-compress.log';
141 }
142
143 /**
144 * Logs an error message.
145 *
146 * @param string $message The message to log.
147 * @param array $context Optional. Additional context data. Default empty array.
148 */
149 public static function error( $message, $context = array() ) {
150 $instance = self::get_instance();
151 $instance->log( self::LOG_LEVEL_ERROR, $message, $context );
152 }
153
154 /**
155 * Logs a debug message.
156 *
157 * @param string $message The message to log.
158 * @param array $context Optional. Additional context data. Default empty array.
159 */
160 public static function debug( $message, $context = array() ) {
161 $instance = self::get_instance();
162 $instance->log( self::LOG_LEVEL_DEBUG, $message, $context );
163 }
164
165 /**
166 * Logs a message.
167 *
168 * @param string $level The log level.
169 * @param string $message The message to log.
170 * @param array $context Optional. Additional context data. Default empty array.
171 * @return void
172 */
173 private function log( $level, $message, $context = array() ) {
174 if ( ! $this->log_enabled ) {
175 return;
176 }
177
178 $this->rotate_logs();
179
180 // Ensure log directory exists.
181 $log_dir = dirname( $this->log_file_path );
182 $wp_filesystem = Tiny_Helpers::get_wp_filesystem();
183 if ( ! $wp_filesystem->exists( $log_dir ) ) {
184 wp_mkdir_p( $log_dir );
185 self::create_blocking_files( $log_dir );
186 }
187
188 $timestamp = current_time( 'Y-m-d H:i:s' );
189 $level_str = strtoupper( $level );
190 $context_str = ! empty( $context ) ? ' ' . wp_json_encode( $context ) : '';
191 $log_entry = "[{$timestamp}] [{$level_str}] {$message}{$context_str}" . PHP_EOL;
192
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