PluginProbe
ezCache / 2.5
ezCache v2.5
2.6.5 2.6.4 2.6.2 2.6.3 2.6.1 2.6.0 2.5.6 2.5.5 2.5.4 2.5.3 2.5.2 2.5.1 2.5 2.2.1 2.2.2 trunk 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.3 1.3.1 1.3.10 1.3.11 All 49 releases
ezcache / includes / Rest / WebpController.php

WebpController.php in ezCache 2.5, at includes/Rest/WebpController.php

206 lines 6.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Upress\EzCache\Rest;
3
4 use Upress\EzCache\Cache;
5 use Upress\EzCache\PremiumFeatures;
6 use Upress\EzCache\WebpApi;
7 use Upress\EzCache\Utilities\Logger;
8
9 class WebpController {
10
11 /**
12 * Generate webp URL matching original WebpConverter format
13 * e.g., image.jpg → image.4a5fec.webp (md5 suffix of extension)
14 */
15 private static function make_webp_name( $path_or_url ) {
16 $ext = pathinfo( $path_or_url, PATHINFO_EXTENSION );
17 $ext_hash = substr( md5( $ext ), -6 );
18 return preg_replace( '/\.' . preg_quote( $ext, '/' ) . '$/', '.' . $ext_hash . '.webp', $path_or_url );
19 }
20
21 function status() {
22 global $wpdb;
23 $table = $wpdb->prefix . 'ezcache_webp_images';
24
25 if ( $wpdb->get_var( "SHOW TABLES LIKE '{$table}'" ) !== $table ) {
26 return [ 'success' => true, 'data' => [
27 'total' => 0, 'completed' => 0, 'pending' => 0, 'failed' => 0,
28 'saved_bytes' => 0, 'is_premium' => PremiumFeatures::is_premium(),
29 ]];
30 }
31
32 $total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" );
33 $completed = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}` WHERE status = 'completed'" );
34 $pending = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}` WHERE status = 'pending'" );
35 $failed = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}` WHERE status = 'failed'" );
36 $saved = $wpdb->get_row( "SELECT COALESCE(SUM(original_size),0) as orig, COALESCE(SUM(webp_size),0) as webp FROM `{$table}` WHERE status = 'completed'" );
37 $saved_bytes = $saved ? (int)$saved->orig - (int)$saved->webp : 0;
38
39 return [ 'success' => true, 'data' => [
40 'total' => $total, 'completed' => $completed, 'pending' => $pending,
41 'failed' => $failed, 'saved_bytes' => max(0, $saved_bytes),
42 'is_premium' => PremiumFeatures::is_premium(),
43 ]];
44 }
45
46 function scan() {
47 global $wpdb;
48 if ( ! PremiumFeatures::is_premium() ) {
49 return new \WP_Error( 'premium_required', 'Premium license required', [ 'status' => 403 ] );
50 }
51
52 $table = $wpdb->prefix . 'ezcache_webp_images';
53 $attachments = $wpdb->get_results(
54 "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'attachment'
55 AND post_mime_type IN ('image/jpeg','image/png','image/gif') ORDER BY ID DESC"
56 );
57
58 $queued = 0;
59 foreach ( $attachments as $att ) {
60 $file = get_attached_file( $att->ID );
61 if ( ! $file || ! file_exists( $file ) ) continue;
62
63 $url = wp_get_attachment_url( $att->ID );
64 $files_to_process = [ [ 'path' => $file, 'url' => $url ] ];
65
66 // Add thumbnails
67 $meta = wp_get_attachment_metadata( $att->ID );
68 if ( isset( $meta['sizes'] ) ) {
69 $dir = dirname( $file );
70 $url_dir = dirname( $url );
71 foreach ( $meta['sizes'] as $data ) {
72 $tp = $dir . '/' . $data['file'];
73 $tu = $url_dir . '/' . $data['file'];
74 if ( file_exists( $tp ) ) {
75 $files_to_process[] = [ 'path' => $tp, 'url' => $tu ];
76 }
77 }
78 }
79
80 foreach ( $files_to_process as $item ) {
81 $uid = sha1( $item['path'] );
82 $exists = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM `{$table}` WHERE uid = %s", $uid ) );
83 if ( $exists ) continue;
84
85 $webp_url = self::make_webp_name( $item['url'] );
86 $webp_path = self::make_webp_name( $item['path'] );
87
88 $wpdb->insert( $table, [
89 'uid' => $uid, 'url' => $item['url'], 'path' => $item['path'],
90 'webp_url' => $webp_url, 'webp_path' => $webp_path,
91 'status' => 'pending',
92 'created_at' => current_time( 'mysql' ),
93 'updated_at' => current_time( 'mysql' ),
94 ]);
95 if ( $wpdb->insert_id ) $queued++;
96 }
97 }
98
99 return [ 'success' => true, 'data' => [ 'queued' => $queued, 'message' => "{$queued} images queued" ] ];
100 }
101
102 function process() {
103 global $wpdb;
104 if ( ! PremiumFeatures::is_premium() ) {
105 return new \WP_Error( 'premium_required', 'Premium required', [ 'status' => 403 ] );
106 }
107
108 $table = $wpdb->prefix . 'ezcache_webp_images';
109 $pending = $wpdb->get_results( $wpdb->prepare(
110 "SELECT * FROM `{$table}` WHERE status = 'pending' ORDER BY id ASC LIMIT %d", 5
111 ));
112
113 if ( empty( $pending ) ) {
114 return [ 'success' => true, 'data' => [ 'processed' => 0, 'remaining' => 0, 'message' => 'Done' ] ];
115 }
116
117 // Freemius removed — Pro is unlocked for everyone.
118 $converter = new WebpApi( 'unlocked_pro' );
119 $processed = 0;
120
121 foreach ( $pending as $image ) {
122 if ( ! file_exists( $image->path ) ) {
123 $wpdb->update( $table, [ 'status' => 'failed', 'updated_at' => current_time('mysql') ], [ 'id' => $image->id ] );
124 continue;
125 }
126
127 $result = $converter->convert( $image->path );
128 if ( is_wp_error( $result ) ) {
129 $wpdb->update( $table, [ 'status' => 'failed', 'updated_at' => current_time('mysql') ], [ 'id' => $image->id ] );
130 Logger::log( "WebP failed: {$image->url}" . $result->get_error_message() );
131 continue;
132 }
133
134 $dir = dirname( $image->webp_path );
135 if ( ! is_dir( $dir ) ) wp_mkdir_p( $dir );
136 file_put_contents( $image->webp_path, $result['data'] );
137
138 $wpdb->update( $table, [
139 'status' => 'completed',
140 'original_size' => filesize( $image->path ),
141 'webp_size' => filesize( $image->webp_path ),
142 'updated_at' => current_time('mysql'),
143 ], [ 'id' => $image->id ] );
144 $processed++;
145 }
146
147 $remaining = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}` WHERE status = 'pending'" );
148
149 // If done, update existing cache files
150 if ( $remaining === 0 ) {
151 $this->update_cache_files();
152 }
153
154 return [ 'success' => true, 'data' => [ 'processed' => $processed, 'remaining' => $remaining ] ];
155 }
156
157 /**
158 * After all conversions done, update cached HTML files to use WebP URLs
159 */
160 private function update_cache_files() {
161 global $wpdb;
162 $table = $wpdb->prefix . 'ezcache_webp_images';
163
164 $completed = $wpdb->get_results( "SELECT url, webp_url FROM `{$table}` WHERE status = 'completed'" );
165 if ( empty( $completed ) ) return;
166
167 $upload_dir = wp_upload_dir();
168 $cache_dir = WP_CONTENT_DIR . '/cache/ezcache/';
169 if ( ! is_dir( $cache_dir ) ) return;
170
171 $cache_files = glob( $cache_dir . '*/*-webp.html.gz' );
172 if ( ! $cache_files ) return;
173
174 foreach ( $cache_files as $file ) {
175 $content = @gzdecode( file_get_contents( $file ) );
176 if ( ! $content ) continue;
177
178 $changed = false;
179 foreach ( $completed as $img ) {
180 if ( strpos( $content, $img->url ) !== false ) {
181 $content = str_replace( $img->url, $img->webp_url, $content );
182 $changed = true;
183 }
184 }
185
186 if ( $changed ) {
187 file_put_contents( $file, gzencode( $content, 6, FORCE_GZIP ) );
188 }
189 }
190 }
191
192 function destroy() {
193 global $wpdb;
194 $table = $wpdb->prefix . 'ezcache_webp_images';
195
196 $images = $wpdb->get_results( "SELECT webp_path FROM `{$table}` WHERE status = 'completed'" );
197 foreach ( $images as $img ) {
198 if ( file_exists( $img->webp_path ) ) @unlink( $img->webp_path );
199 }
200 $wpdb->query( "TRUNCATE TABLE `{$table}`" );
201 Cache::instance()->clear_cache( true );
202
203 return [ 'success' => true, 'data' => [ 'message' => 'Cleared' ] ];
204 }
205 }
206