PluginProbe ʕ •ᴥ•ʔ
TinyPNG – JPEG, PNG & WebP image compression / 3.6.8
TinyPNG – JPEG, PNG & WebP image compression v3.6.8
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 4 years ago images 4 years ago js 5 months ago vendor 1 year ago views 5 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-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 5 months ago class-tiny-notices.php 5 months ago class-tiny-php.php 5 months ago class-tiny-picture.php 5 months ago class-tiny-plugin.php 5 months ago class-tiny-settings.php 5 months ago class-tiny-source-base.php 5 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
264 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 * - if turn on, clear the old logs
112 */
113 public static function on_save_log_enabled( $log_enabled, $old, $option ) {
114 $instance = self::get_instance();
115 $instance->log_enabled = 'on' === $log_enabled;
116 if ( $instance->get_log_enabled() ) {
117 self::clear_logs();
118 }
119
120 return $log_enabled;
121 }
122
123 /**
124 * Retrieves the log path using wp_upload_dir. This operation
125 * should only be used internally. Use the getter to get the
126 * memoized function.
127 *
128 * @return string The log file path.
129 */
130 private function resolve_log_file_path() {
131 $upload_dir = wp_upload_dir();
132 $log_dir = trailingslashit( $upload_dir['basedir'] ) . 'tiny-compress-logs';
133 return trailingslashit( $log_dir ) . 'tiny-compress.log';
134 }
135
136 /**
137 * Logs an error message.
138 *
139 * @param string $message The message to log.
140 * @param array $context Optional. Additional context data. Default empty array.
141 */
142 public static function error( $message, $context = array() ) {
143 $instance = self::get_instance();
144 $instance->log( self::LOG_LEVEL_ERROR, $message, $context );
145 }
146
147 /**
148 * Logs a debug message.
149 *
150 * @param string $message The message to log.
151 * @param array $context Optional. Additional context data. Default empty array.
152 */
153 public static function debug( $message, $context = array() ) {
154 $instance = self::get_instance();
155 $instance->log( self::LOG_LEVEL_DEBUG, $message, $context );
156 }
157
158 /**
159 * Logs a message.
160 *
161 * @param string $level The log level.
162 * @param string $message The message to log.
163 * @param array $context Optional. Additional context data. Default empty array.
164 * @return void
165 */
166 private function log( $level, $message, $context = array() ) {
167 if ( ! $this->log_enabled ) {
168 return;
169 }
170
171 $this->rotate_logs();
172
173 // Ensure log directory exists.
174 $log_dir = dirname( $this->log_file_path );
175 $wp_filesystem = Tiny_Helpers::get_wp_filesystem();
176 if ( ! $wp_filesystem->exists( $log_dir ) ) {
177 wp_mkdir_p( $log_dir );
178 self::create_blocking_files( $log_dir );
179 }
180
181 $timestamp = current_time( 'Y-m-d H:i:s' );
182 $level_str = strtoupper( $level );
183 $context_str = ! empty( $context ) ? ' ' . wp_json_encode( $context ) : '';
184 $log_entry = "[{$timestamp}] [{$level_str}] {$message}{$context_str}" . PHP_EOL;
185
186 error_log( $log_entry, 3, $this->log_file_path );
187 }
188
189 /**
190 * Deletes log file and creates a new one when the
191 * MAX_LOG_SIZE is met.
192 *
193 * @return void
194 */
195 private function rotate_logs() {
196 $wp_filesystem = Tiny_Helpers::get_wp_filesystem();
197 if ( ! $wp_filesystem->exists( $this->log_file_path ) ) {
198 return;
199 }
200
201 $file_size = $wp_filesystem->size( $this->log_file_path );
202 if ( $file_size < self::MAX_LOG_SIZE ) {
203 return;
204 }
205
206 $wp_filesystem->delete( $this->log_file_path );
207 }
208
209 /**
210 * Clears log file
211 *
212 * @return bool True if logs were cleared successfully.
213 */
214 public static function clear_logs() {
215 $instance = self::get_instance();
216 $log_path = $instance->get_log_file_path();
217 $wp_filesystem = Tiny_Helpers::get_wp_filesystem();
218 $file_exits = $wp_filesystem->exists( $log_path );
219 if ( $file_exits ) {
220 return $wp_filesystem->delete( $log_path );
221 }
222
223 return true;
224 }
225
226 /**
227 * Creates defensive files to prevent direct access to log directory.
228 * Adds index.html to prevent directory listing and .htaccess to block access.
229 *
230 * @param string $log_dir The path to the log directory.
231 * @return void
232 */
233 private static function create_blocking_files( $log_dir ) {
234 $wp_filesystem = Tiny_Helpers::get_wp_filesystem();
235
236 $index_file = trailingslashit( $log_dir ) . 'index.html';
237 if ( ! $wp_filesystem->exists( $index_file ) ) {
238 $index_content = '<!-- Silence is golden -->';
239 $wp_filesystem->put_contents( $index_file, $index_content, FS_CHMOD_FILE );
240 }
241
242 $htaccess_file = trailingslashit( $log_dir ) . '.htaccess';
243 if ( ! $wp_filesystem->exists( $htaccess_file ) ) {
244 $htaccess_content = 'deny from all';
245 $wp_filesystem->put_contents( $htaccess_file, $htaccess_content, FS_CHMOD_FILE );
246 }
247 }
248
249 /**
250 * Gets all log file paths.
251 *
252 * @return array Array of log file paths.
253 */
254 public function get_log_files() {
255 $files = array();
256
257 if ( file_exists( $this->log_file_path ) ) {
258 $files[] = $this->log_file_path;
259 }
260
261 return $files;
262 }
263 }
264