W3 Total Cache - Version 2.2.0

Version Description

  • Feature: Image Service API extension: WebP conversion options
Download this release

Release Info

Developer joemoto
Plugin Icon 128x128 W3 Total Cache
Version 2.2.0
Comparing to
See all releases

Code changes from version 2.1.9 to 2.2.0

BrowserCache_Environment_Nginx.php CHANGED
@@ -264,55 +264,70 @@ class BrowserCache_Environment_Nginx {
264
  return $rules;
265
  }
266
 
267
-
268
-
269
  /**
270
- * Adds cache rules for type to &$rules
271
  *
272
- * @param string $rules
273
- * @param array $mime_types
274
- * @param string $section
275
  * @return void
276
  */
277
  private function generate_section( &$rules, $mime_types, $section ) {
278
- $expires = $this->c->get_boolean( 'browsercache.' . $section . '.expires' );
279
- $etag = $this->c->get_boolean( 'browsercache.' . $section . '.etag' );
280
  $cache_control = $this->c->get_boolean( 'browsercache.' . $section . '.cache.control' );
281
- $w3tc = $this->c->get_boolean( 'browsercache.' . $section . '.w3tc' );
282
  $last_modified = $this->c->get_boolean( 'browsercache.' . $section . '.last_modified' );
283
 
284
- if ( $etag || $expires || $cache_control || $w3tc || !$last_modified ) {
285
- $lifetime = $this->c->get_integer( 'browsercache.' . $section . '.lifetime' );
286
-
287
- $mime_types2 = apply_filters( 'w3tc_browsercache_rules_section_extensions',
288
- $mime_types, $this->c, $section );
289
- $extensions = array_keys( $mime_types2 );
 
 
290
 
291
- // Remove ext from filesmatch if its the same as permalink extension
292
  $pext = strtolower( pathinfo( get_option( 'permalink_structure' ), PATHINFO_EXTENSION ) );
 
293
  if ( $pext ) {
294
  $extensions = Util_Rule::remove_extension_from_list( $extensions, $pext );
295
  }
296
 
297
- $rules .= "location ~ \\.(" . implode( '|', $extensions ) . ")$ {\n";
 
 
 
298
 
299
- $subrules = Dispatcher::nginx_rules_for_browsercache_section(
300
- $this->c, $section );
301
- $rules .= ' ' . implode( "\n ", $subrules ) . "\n";
 
 
 
 
 
302
 
303
- if ( !$this->c->get_boolean( 'browsercache.no404wp' ) ) {
 
 
 
 
 
 
 
 
 
304
  $wp_uri = network_home_url( '', 'relative' );
305
  $wp_uri = rtrim( $wp_uri, '/' );
306
-
307
- $rules .= ' try_files $uri $uri/ ' . $wp_uri .
308
- '/index.php?$args;' . "\n";
309
  }
310
- $rules .= "}\n";
 
311
  }
312
  }
313
 
314
-
315
-
316
  /**
317
  * Returns directives plugin applies to files of specific section
318
  * Without location
264
  return $rules;
265
  }
266
 
 
 
267
  /**
268
+ * Adds cache rules for type to &$rules.
269
  *
270
+ * @param string $rules Rules.
271
+ * @param array $mime_types MIME types.
272
+ * @param string $section Section.
273
  * @return void
274
  */
275
  private function generate_section( &$rules, $mime_types, $section ) {
276
+ $expires = $this->c->get_boolean( 'browsercache.' . $section . '.expires' );
277
+ $etag = $this->c->get_boolean( 'browsercache.' . $section . '.etag' );
278
  $cache_control = $this->c->get_boolean( 'browsercache.' . $section . '.cache.control' );
279
+ $w3tc = $this->c->get_boolean( 'browsercache.' . $section . '.w3tc' );
280
  $last_modified = $this->c->get_boolean( 'browsercache.' . $section . '.last_modified' );
281
 
282
+ if ( $etag || $expires || $cache_control || $w3tc || ! $last_modified ) {
283
+ $mime_types2 = apply_filters(
284
+ 'w3tc_browsercache_rules_section_extensions',
285
+ $mime_types,
286
+ $this->c,
287
+ $section
288
+ );
289
+ $extensions = array_keys( $mime_types2 );
290
 
291
+ // Remove ext from filesmatch if its the same as permalink extension.
292
  $pext = strtolower( pathinfo( get_option( 'permalink_structure' ), PATHINFO_EXTENSION ) );
293
+
294
  if ( $pext ) {
295
  $extensions = Util_Rule::remove_extension_from_list( $extensions, $pext );
296
  }
297
 
298
+ $rules .= 'location ~ \\.(' . implode( '|', $extensions ) . ')$ {' . "\n";
299
+
300
+ $subrules = Dispatcher::nginx_rules_for_browsercache_section( $this->c, $section );
301
+ $rules .= ' ' . implode( "\n ", $subrules ) . "\n";
302
 
303
+ // Add rules for the Image Service extension, if active.
304
+ if ( 'other' === $section && array_key_exists( 'imageservice', $this->c->get_array( 'extensions.active' ) ) ) {
305
+ $rules .= "\n" . ' location ~* ^(?<path>.+)\.(jpe?g|png|gif)$ {' . "\n" .
306
+ ' if ( $http_accept !~* "webp|\*/\*" ) {' . "\n" .
307
+ ' break;' . "\n" .
308
+ ' }' . "\n\n" .
309
+ ' ' . implode( "\n ", Dispatcher::nginx_rules_for_browsercache_section( $this->c, $section, true ) ) . "\n" .
310
+ ' add_header Vary Accept;' . "\n";
311
 
312
+ if ( $this->c->get_boolean( 'browsercache.no404wp' ) ) {
313
+ $rules .= ' try_files ${path}.webp $uri =404;';
314
+ } else {
315
+ $rules .= ' try_files ${path}.webp $uri /index.php?$args;';
316
+ }
317
+
318
+ $rules .= "\n" . ' }' . "\n\n";
319
+ }
320
+
321
+ if ( ! $this->c->get_boolean( 'browsercache.no404wp' ) ) {
322
  $wp_uri = network_home_url( '', 'relative' );
323
  $wp_uri = rtrim( $wp_uri, '/' );
324
+ $rules .= ' try_files $uri $uri/ ' . $wp_uri . '/index.php?$args;' . "\n";
 
 
325
  }
326
+
327
+ $rules .= '}' . "\n";
328
  }
329
  }
330
 
 
 
331
  /**
332
  * Returns directives plugin applies to files of specific section
333
  * Without location
ConfigCompiler.php CHANGED
@@ -348,7 +348,6 @@ class ConfigCompiler {
348
  'w3-total-cache/Extension_NewRelic_Plugin.php';
349
  $file_data['extensions.active']['fragmentcache'] =
350
  'w3-total-cache/Extension_FragmentCache_Plugin.php';
351
-
352
  }
353
 
354
  // newrelic settings - migrate to extension
348
  'w3-total-cache/Extension_NewRelic_Plugin.php';
349
  $file_data['extensions.active']['fragmentcache'] =
350
  'w3-total-cache/Extension_FragmentCache_Plugin.php';
 
351
  }
352
 
353
  // newrelic settings - migrate to extension
ConfigKeys.php CHANGED
@@ -2421,12 +2421,12 @@ $keys = array(
2421
  'extensions.active' => array(
2422
  'type' => 'array',
2423
  'default' => array(
2424
- 'fragmentcache' => 'w3-total-cache/Extension_FragmentCache_Plugin.php'
2425
- )
2426
  ),
2427
  'extensions.active_frontend' => array(
2428
  'type' => 'array',
2429
- 'default' => array()
2430
  ),
2431
  'extensions.active_dropin' => array(
2432
  'type' => 'array',
@@ -2446,6 +2446,13 @@ $keys = array(
2446
  'type' => 'boolean',
2447
  'default' => false,
2448
  ),
 
 
 
 
 
 
 
2449
 
2450
  // extensions keys:
2451
  //
2421
  'extensions.active' => array(
2422
  'type' => 'array',
2423
  'default' => array(
2424
+ 'fragmentcache' => 'w3-total-cache/Extension_FragmentCache_Plugin.php',
2425
+ ),
2426
  ),
2427
  'extensions.active_frontend' => array(
2428
  'type' => 'array',
2429
+ 'default' => array(),
2430
  ),
2431
  'extensions.active_dropin' => array(
2432
  'type' => 'array',
2446
  'type' => 'boolean',
2447
  'default' => false,
2448
  ),
2449
+ 'imageservice' => array(
2450
+ 'type' => 'array',
2451
+ 'default' => array(
2452
+ 'compression' => 'lossy',
2453
+ 'auto' => 'enabled',
2454
+ ),
2455
+ ),
2456
 
2457
  // extensions keys:
2458
  //
Extension_ImageService_Api.php ADDED
@@ -0,0 +1,390 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * File: Extension_ImageService_Api.php
4
+ *
5
+ * @since 2.2.0
6
+ *
7
+ * @package W3TC
8
+ */
9
+
10
+ namespace W3TC;
11
+
12
+ /**
13
+ * Class: Extension_ImageService_Api
14
+ *
15
+ * @since 2.2.0
16
+ */
17
+ class Extension_ImageService_Api {
18
+ /**
19
+ * API Base URL.
20
+ *
21
+ * @since 2.2.0
22
+ * @access private
23
+ *
24
+ * @var string
25
+ */
26
+ private $base_url = 'https://api2.w3-edge.com';
27
+
28
+ /**
29
+ * W3TC Pro license key.
30
+ *
31
+ * @since 2.2.0
32
+ * @access private
33
+ *
34
+ * @var string
35
+ */
36
+ private $license_key = '0';
37
+
38
+ /**
39
+ * W3TC Pro licensed home URL.
40
+ *
41
+ * @since 2.2.0
42
+ * @access private
43
+ *
44
+ * @var string
45
+ */
46
+ private $home_url;
47
+
48
+ /**
49
+ * W3TC Pro licensed product item name.
50
+ *
51
+ * @since 2.2.0
52
+ * @access private
53
+ *
54
+ * @var string
55
+ */
56
+ private $item_name = '0';
57
+
58
+ /**
59
+ * API endpoints.
60
+ *
61
+ * @since 2.2.0
62
+ * @access private
63
+ *
64
+ * @var array
65
+ */
66
+ private $endpoints = array(
67
+ 'convert' => array(
68
+ 'method' => 'POST',
69
+ 'uri' => '/image/convert',
70
+ ),
71
+ 'status' => array(
72
+ 'method' => 'GET',
73
+ 'uri' => '/job/status',
74
+ ),
75
+ 'download' => array(
76
+ 'method' => 'GET',
77
+ 'uri' => '/image/download',
78
+ ),
79
+ 'usage' => array(
80
+ 'method' => 'GET',
81
+ 'uri' => '/image/usage',
82
+ ),
83
+ );
84
+
85
+ /**
86
+ * Constructor.
87
+ *
88
+ * @since 2.2.0
89
+ */
90
+ public function __construct() {
91
+ $config = Dispatcher::config();
92
+
93
+ if ( Util_Environment::is_w3tc_pro( $config ) ) {
94
+ $this->license_key = $config->get_string( 'plugin.license_key' );
95
+ $this->home_url = network_home_url();
96
+ $this->item_name = W3TC_PURCHASE_PRODUCT_NAME;
97
+ } else {
98
+ $this->home_url = md5( network_home_url() );
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Convert an image; submit a job request.
104
+ *
105
+ * @since 2.2.0
106
+ *
107
+ * @param string $filepath Image file path.
108
+ * @param array $options Optional array of options. Overrides settings.
109
+ * @return array
110
+ */
111
+ public function convert( $filepath, array $options = array() ) {
112
+ $config = Dispatcher::config();
113
+ $settings = $config->get_array( 'imageservice' );
114
+ $options = array_merge(
115
+ array(
116
+ 'optimize' => 'lossy' === $settings['compression'] ? '1' : '0',
117
+ ),
118
+ $options
119
+ );
120
+ $boundary = wp_generate_password( 24 );
121
+ $body = '';
122
+
123
+ $post_fields = array(
124
+ 'license_key' => $this->license_key,
125
+ 'home_url' => $this->home_url,
126
+ 'item_name' => $this->item_name,
127
+ 'optimize' => $options['optimize'],
128
+ );
129
+
130
+ foreach ( $post_fields as $k => $v ) {
131
+ $body .= '--' . $boundary . "\r\n";
132
+ $body .= 'Content-Disposition: form-data; name="' . $k . '"' . "\r\n\r\n";
133
+ $body .= $v . "\r\n";
134
+ }
135
+
136
+ $body .= '--' . $boundary . "\r\n";
137
+ $body .= 'Content-Disposition: form-data; name="image"; filename="' . basename( $filepath ) . '"' . "\r\n\r\n";
138
+ $body .= file_get_contents( $filepath ) . "\r\n" . '--' . $boundary . '--'; // phpcs:ignore WordPress.WP.AlternativeFunctions
139
+
140
+ $response = wp_remote_request(
141
+ $this->get_base_url() . $this->endpoints['convert']['uri'],
142
+ array(
143
+ 'method' => $this->endpoints['convert']['method'],
144
+ 'sslverify' => false,
145
+ 'timeout' => 30,
146
+ 'headers' => array(
147
+ 'Accept' => 'application/json',
148
+ 'Content-Type' => 'multipart/form-data; boundary=' . $boundary,
149
+ ),
150
+ 'body' => $body,
151
+ )
152
+ );
153
+
154
+ if ( is_wp_error( $response ) ) {
155
+ return array(
156
+ 'error' => __( 'WP Error: ', 'w3-total-cache' ) . $response->get_error_message(),
157
+ );
158
+ }
159
+
160
+ // Convert response body to an array.
161
+ $response_body = json_decode( wp_remote_retrieve_body( $response ), true );
162
+
163
+ // Update usage.
164
+ if ( isset( $response_body['usage_hourly'] ) ) {
165
+ set_transient(
166
+ 'w3tc_imageservice_usage',
167
+ array(
168
+ 'updated_at' => time(),
169
+ 'usage_hourly' => $response_body['usage_hourly'],
170
+ 'usage_monthly' => isset( $response_body['usage_monthly'] ) ? $response_body['usage_monthly'] : null,
171
+ 'limit_hourly' => isset( $response_body['limit_hourly'] ) ? $response_body['limit_hourly'] : null,
172
+ 'limit_monthly' => isset( $response_body['limit_monthly'] ) ? $response_body['limit_monthly'] : null,
173
+ ),
174
+ DAY_IN_SECONDS
175
+ );
176
+ }
177
+
178
+ // Handle non-200 response codes.
179
+ if ( 200 !== $response['response']['code'] ) {
180
+ $result = array(
181
+ 'code' => $response['response']['code'],
182
+ 'error' => esc_html__( 'Error: Received a non-200 response code: ', 'w3-total-cache' ) . $response['response']['code'],
183
+ );
184
+
185
+ if ( isset( $response_body['error']['id'] ) && 'exceeded-hourly' === $response_body['error']['id'] ) {
186
+ $result['message'] = sprintf(
187
+ // translators: 1: Hourly request limit.
188
+ esc_html__( 'You reached your hourly limit of %1$d; try again later%2$s.', 'w3-total-cache' ),
189
+ esc_attr( $response_body['limit_hourly'] ),
190
+ isset( $response_body['licensed'] ) && $response_body['licensed'] ? '' :
191
+ sprintf(
192
+ // translators: 1: Hourly request limit, 2: HTML anchor open tag, 3: HTML anchor close tag.
193
+ esc_html__( ' or %1$supgrade to Pro%2$s for higher limits', 'w3-total-cache' ),
194
+ '<a href="#" class="button-buy-plugin" data-src="imageservice_api_limit">',
195
+ '</a>'
196
+ )
197
+ );
198
+ } elseif ( isset( $response_body['error']['id'] ) && 'exceeded-monthly' === $response_body['error']['id'] ) {
199
+ $result['message'] = sprintf(
200
+ // translators: 1: Monthly request limit, 2: HTML anchor open tag, 3: HTML anchor close tag.
201
+ esc_html__( 'You reached your monthly limit of %1$d; try again later or %2$supgrade to Pro%3$s for unlimited.', 'w3-total-cache' ),
202
+ esc_attr( $response_body['limit_monthly'] ),
203
+ '<a href="#" class="button-buy-plugin" data-src="imageservice_api_limit">',
204
+ '</a>'
205
+ );
206
+ } elseif ( isset( $response_body['error']['id'] ) && 'invalid-output-mime' === $response_body['error']['id'] ) {
207
+ $result['message'] = esc_html__( 'Invalid output image MIME type.', 'w3-total-cache' );
208
+ } elseif ( isset( $response_body['error']['id'] ) && 'missing-image' === $response_body['error']['id'] ) {
209
+ $result['message'] = esc_html__( 'An image file is required.', 'w3-total-cache' );
210
+ } elseif ( isset( $response_body['error']['id'] ) && 'invalid-image' === $response_body['error']['id'] ) {
211
+ $result['message'] = esc_html__( 'Valid image data is required.', 'w3-total-cache' );
212
+ } elseif ( isset( $response_body['error']['id'] ) && 'invalid-input-mime' === $response_body['error']['id'] ) {
213
+ $result['message'] = esc_html__( 'Invalid input image MIME type.', 'w3-total-cache' );
214
+ } elseif ( 403 === $response['response']['code'] ) {
215
+ $result['message'] = sprintf(
216
+ // translators: 1: HTML anchor open tag, 2: HTML anchor close tag.
217
+ esc_html__( 'Please verify your license key in %1$sGeneral Settings%2$s.', 'w3-total-cache' ),
218
+ '<a href="' . esc_url( Util_Ui::admin_url( 'admin.php?page=w3tc_general#licensing' ) ) . '">',
219
+ '</a>'
220
+ );
221
+ } elseif ( isset( $response_body['error']['message'] ) ) {
222
+ // Unknown error message id; forward the error message.
223
+ $result['message'] = esc_html( $response_body['error']['message'] );
224
+ }
225
+
226
+ return $result;
227
+ }
228
+
229
+ return $response_body;
230
+ }
231
+
232
+ /**
233
+ * Get job status.
234
+ *
235
+ * @since 2.2.0
236
+ *
237
+ * @param int $job_id Job id.
238
+ * @param string $signature Signature.
239
+ * @return array
240
+ */
241
+ public function get_status( $job_id, $signature ) {
242
+ $response = wp_remote_request(
243
+ $this->get_base_url() . $this->endpoints['status']['uri'] . '/' . $job_id . '/' . $signature,
244
+ array(
245
+ 'method' => $this->endpoints['status']['method'],
246
+ 'sslverify' => false,
247
+ 'timeout' => 10,
248
+ 'headers' => array(
249
+ 'Accept' => 'application/json',
250
+ ),
251
+ )
252
+ );
253
+
254
+ if ( is_wp_error( $response ) ) {
255
+ return array(
256
+ 'error' => __( 'WP Error: ', 'w3-total-cache' ) . $response->get_error_message(),
257
+ );
258
+ }
259
+
260
+ // Convert response body to an array.
261
+ $response = json_decode( wp_remote_retrieve_body( $response ), true );
262
+
263
+ // Pass error message.
264
+ if ( isset( $response['error'] ) ) {
265
+ return array(
266
+ 'error' => $response['error'],
267
+ );
268
+ }
269
+
270
+ return $response;
271
+ }
272
+
273
+ /**
274
+ * Download a processed image.
275
+ *
276
+ * @since 2.2.0
277
+ *
278
+ * @param int $job_id Job id.
279
+ * @param string $signature Signature.
280
+ * @return array WP response array.
281
+ */
282
+ public function download( $job_id, $signature ) {
283
+ $response = wp_remote_request(
284
+ $this->get_base_url() . $this->endpoints['download']['uri'] . '/' . $job_id . '/' . $signature,
285
+ array(
286
+ 'method' => $this->endpoints['download']['method'],
287
+ 'sslverify' => false,
288
+ 'timeout' => 10,
289
+ )
290
+ );
291
+
292
+ if ( is_wp_error( $response ) ) {
293
+ return array(
294
+ 'error' => __( 'WP Error: ', 'w3-total-cache' ) . $response->get_error_message(),
295
+ );
296
+ }
297
+
298
+ // Get the response body.
299
+ $body = wp_remote_retrieve_body( $response );
300
+
301
+ // Convert response body to an array. A successful image results in a JSON decode false return.
302
+ $json = json_decode( $body, true );
303
+
304
+ // Pass error message.
305
+ if ( isset( $json['error'] ) ) {
306
+ return array(
307
+ 'error' => $json['error'],
308
+ );
309
+ }
310
+
311
+ return $response;
312
+ }
313
+
314
+ /**
315
+ * Get usage statistics.
316
+ *
317
+ * @since 2.2.0
318
+ *
319
+ * @return array
320
+ */
321
+ public function get_usage() {
322
+ $error_message = __( 'Unknown', 'w3-total-cache' );
323
+ $error_response = array(
324
+ 'usage_hourly' => $error_message,
325
+ 'usage_monthly' => $error_message,
326
+ 'limit_hourly' => $error_message,
327
+ 'limit_monthly' => $error_message,
328
+ );
329
+
330
+ $response = wp_remote_request(
331
+ esc_url(
332
+ $this->get_base_url() . $this->endpoints['usage']['uri'] .
333
+ '/' . rawurlencode( $this->license_key ) .
334
+ '/' . urlencode( $this->item_name ) . // phpcs:ignore
335
+ '/' . rawurlencode( $this->home_url )
336
+ ),
337
+ array(
338
+ 'method' => $this->endpoints['usage']['method'],
339
+ 'sslverify' => false,
340
+ 'timeout' => 10,
341
+ 'headers' => array(
342
+ 'Accept' => 'application/json',
343
+ ),
344
+ )
345
+ );
346
+
347
+ if ( is_wp_error( $response ) ) {
348
+ return $error_response;
349
+ }
350
+
351
+ // Convert response body to an array.
352
+ $response = json_decode( wp_remote_retrieve_body( $response ), true );
353
+
354
+ // If usage is not obtained, then return error response.
355
+ if ( ! isset( $response['usage_hourly'] ) ) {
356
+ return $error_response;
357
+ } else {
358
+ // Update usage.
359
+ set_transient(
360
+ 'w3tc_imageservice_usage',
361
+ array(
362
+ 'updated_at' => time(),
363
+ 'usage_hourly' => $response['usage_hourly'],
364
+ 'usage_monthly' => isset( $response['usage_monthly'] ) ? $response['usage_monthly'] : null,
365
+ 'limit_hourly' => isset( $response['limit_hourly'] ) ? $response['limit_hourly'] : null,
366
+ 'limit_monthly' => isset( $response['limit_monthly'] ) ? $response['limit_monthly'] : null,
367
+ ),
368
+ DAY_IN_SECONDS
369
+ );
370
+
371
+ // Ensure that the monthly limit is represented correctly.
372
+ $response['limit_monthly'] = $response['limit_monthly'] ? $response['limit_monthly'] : __( 'Unlimited', 'w3-total-cache' );
373
+
374
+ return $response;
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Get base URL.
380
+ *
381
+ * @since 2.2.0
382
+ * @access private
383
+ *
384
+ * @returns string
385
+ */
386
+ private function get_base_url() {
387
+ return defined( 'W3TC_API2_URL' ) && W3TC_API2_URL ?
388
+ esc_url( W3TC_API2_URL, 'https', '' ) : $this->base_url;
389
+ }
390
+ }
Extension_ImageService_Cron.php ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * File: Extension_ImageService_Cron.php
4
+ *
5
+ * @since 2.2.0
6
+ *
7
+ * @package W3TC
8
+ */
9
+
10
+ namespace W3TC;
11
+
12
+ /**
13
+ * Class: Extension_ImageService_Cron
14
+ *
15
+ * @since 2.2.0
16
+ */
17
+ class Extension_ImageService_Cron {
18
+ /**
19
+ * Add cron job/event.
20
+ *
21
+ * @since 2.2.0
22
+ * @static
23
+ */
24
+ public static function add_cron() {
25
+ if ( ! wp_next_scheduled( 'w3tc_imageservice_cron' ) ) {
26
+ wp_schedule_event( time(), 'ten_seconds', 'w3tc_imageservice_cron' );
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Add cron schedule.
32
+ *
33
+ * @since 2.2.0
34
+ * @static
35
+ *
36
+ * @param array $schedules Schedules.
37
+ */
38
+ public static function add_schedule( array $schedules = array() ) {
39
+ $schedules['ten_seconds'] = array(
40
+ 'interval' => 10,
41
+ 'display' => esc_html__( 'Every Ten Seconds', 'w3-total-cache' ),
42
+ );
43
+ return $schedules;
44
+ }
45
+
46
+ /**
47
+ * Remove cron job/event.
48
+ *
49
+ * @since 2.2.0
50
+ * @static
51
+ */
52
+ public static function delete_cron() {
53
+ $timestamp = wp_next_scheduled( 'w3tc_imageservice_cron' );
54
+
55
+ if ( $timestamp ) {
56
+ wp_unschedule_event( $timestamp, 'w3tc_imageservice_cron' );
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Run the cron event.
62
+ *
63
+ * @since 2.2.0
64
+ *
65
+ * @see Extension_ImageService_Plugin_Admin::get_imageservice_attachments()
66
+ * @see Extension_ImageService_Plugin::get_api()
67
+ *
68
+ * @global $wp_filesystem WP_Filesystem.
69
+ */
70
+ public static function run() {
71
+ // Get all images with postmeta key "w3tc_imageservice".
72
+ $results = Extension_ImageService_Plugin_Admin::get_imageservice_attachments();
73
+
74
+ // If there are matches, then load dependencies before use.
75
+ if ( $results->have_posts() ) {
76
+ require_once __DIR__ . '/Extension_ImageService_Plugin_Admin.php';
77
+
78
+ $wp_upload_dir = wp_upload_dir();
79
+
80
+ global $wp_filesystem;
81
+
82
+ // Make sure that this file is included, as wp_generate_attachment_metadata() depends on it.
83
+ require_once ABSPATH . 'wp-admin/includes/image.php';
84
+ }
85
+
86
+ foreach ( $results->posts as $post ) {
87
+ $postmeta = get_post_meta( $post->ID, 'w3tc_imageservice', true );
88
+ $status = isset( $postmeta['status'] ) ? $postmeta['status'] : null;
89
+
90
+ // Handle items with the "processing" status.
91
+ if ( 'processing' === $status && isset( $postmeta['processing']['job_id'] ) && isset( $postmeta['processing']['signature'] ) ) {
92
+ // Get the Image Service API object (singlton).
93
+ $api = Extension_ImageService_Plugin::get_api();
94
+
95
+ // Check the status of the request.
96
+ $response = $api->get_status( $postmeta['processing']['job_id'], $postmeta['processing']['signature'] );
97
+
98
+ // Save the status response.
99
+ Extension_ImageService_Plugin_Admin::update_postmeta(
100
+ $post->ID,
101
+ array( 'job_status' => $response )
102
+ );
103
+
104
+ // Check if image is ready for pickup/download.
105
+ if ( isset( $response['status'] ) && 'pickup' === $response['status'] ) {
106
+ // Download image.
107
+ $response = $api->download( $postmeta['processing']['job_id'], $postmeta['processing']['signature'] );
108
+ $headers = wp_remote_retrieve_headers( $response );
109
+ $is_error = isset( $response['error'] );
110
+ $is_reduced = ! $is_error && isset( $headers['x-filesize-reduced'] ) &&
111
+ rtrim( $headers['x-filesize-reduced'], '%' ) > 0;
112
+
113
+ switch ( true ) {
114
+ case $is_error:
115
+ $status = 'error';
116
+ break;
117
+ case $is_reduced:
118
+ $status = 'converted';
119
+ break;
120
+ default:
121
+ $status = 'notconverted';
122
+ break;
123
+ }
124
+
125
+ // Save the download headers or error.
126
+ Extension_ImageService_Plugin_Admin::update_postmeta(
127
+ $post->ID,
128
+ array(
129
+ 'download' => $is_error ? $response['error'] : (array) $headers,
130
+ 'status' => $status,
131
+ )
132
+ );
133
+
134
+ // Skip error responses or if converted image is larger.
135
+ if ( $is_error || ! $is_reduced ) {
136
+ continue;
137
+ }
138
+
139
+ // If an converted file already exists, then delete it before saving the new file.
140
+ if ( isset( $postmeta['post_child'] ) ) {
141
+ wp_delete_attachment( $postmeta['post_child'], true );
142
+ }
143
+
144
+ // Save the file.
145
+ $original_filepath = get_attached_file( $post->ID );
146
+ $original_size = wp_getimagesize( $original_filepath );
147
+ $original_filename = basename( get_attached_file( $post->ID ) );
148
+ $original_filedir = str_replace( '/' . $original_filename, '', $original_filepath );
149
+ $extension = isset( $headers['X-Mime-Type-Out'] ) ?
150
+ str_replace( 'image/', '', $headers['X-Mime-Type-Out'] ) : 'webp';
151
+ $new_filename = preg_replace( '/\.[^.]+$/', '', $original_filename ) . '.' . $extension;
152
+ $new_filepath = $original_filedir . '/' . $new_filename;
153
+
154
+ if ( is_a( $wp_filesystem, 'WP_Filesystem_Base' ) ) {
155
+ $wp_filesystem->put_contents( $new_filepath, wp_remote_retrieve_body( $response ) );
156
+ } else {
157
+ Util_File::file_put_contents_atomic( $new_filepath, wp_remote_retrieve_body( $response ) );
158
+ }
159
+
160
+ // Insert as attachment post.
161
+ $post_id = wp_insert_attachment(
162
+ array(
163
+ 'guid' => $new_filepath,
164
+ 'post_mime_type' => $headers['x-mime-type-out'],
165
+ 'post_title' => preg_replace( '/\.[^.]+$/', '', $new_filename ),
166
+ 'post_content' => '',
167
+ 'post_status' => 'inherit',
168
+ 'post_parent' => $post->ID,
169
+ 'comment_status' => 'closed',
170
+ ),
171
+ $new_filepath,
172
+ $post->ID,
173
+ false,
174
+ false
175
+ );
176
+
177
+ // Copy postmeta data to the new attachment.
178
+ Extension_ImageService_Plugin_Admin::copy_postmeta( $post->ID, $post_id );
179
+
180
+ // Save the new post id.
181
+ Extension_ImageService_Plugin_Admin::update_postmeta(
182
+ $post->ID,
183
+ array( 'post_child' => $post_id )
184
+ );
185
+
186
+ // Mark the downloaded file as the converted one.
187
+ Extension_ImageService_Plugin_Admin::update_postmeta(
188
+ $post_id,
189
+ array( 'is_converted_file' => true )
190
+ );
191
+
192
+ // In order to filter/hide converted files in the media list, add a meta key.
193
+ update_post_meta( $post_id, 'w3tc_imageservice_file', $extension );
194
+
195
+ // Generate the metadata for the attachment, and update the database record.
196
+ $attach_data = wp_generate_attachment_metadata( $post_id, $new_filepath );
197
+ $attach_data['width'] = isset( $attach_data['width'] ) ? $attach_data['width'] : $original_size[0];
198
+ $attach_data['height'] = isset( $attach_data['height'] ) ? $attach_data['height'] : $original_size[1];
199
+ wp_update_attachment_metadata( $post_id, $attach_data );
200
+ } elseif ( isset( $response['status'] ) && 'complete' === $response['status'] ) {
201
+ // Update the status to "error".
202
+ Extension_ImageService_Plugin_Admin::update_postmeta(
203
+ $post->ID,
204
+ array( 'status' => 'error' )
205
+ );
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
Extension_ImageService_Environment.php ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * File: Extension_ImageService_Environment.php
4
+ *
5
+ * @since 2.2.0
6
+ *
7
+ * @package W3TC
8
+ */
9
+
10
+ namespace W3TC;
11
+
12
+ /**
13
+ * Class: Extension_ImageService_Environment
14
+ */
15
+ class Extension_ImageService_Environment {
16
+ /**
17
+ * Fixes environment in each wp-admin request.
18
+ *
19
+ * @since 2.2.0
20
+ *
21
+ * @param Config $config Configuration.
22
+ * @param bool $force_all_checks Force all checks.
23
+ * @throws Util_Environment_Exceptions Exceptions.
24
+ */
25
+ public function fix_on_wpadmin_request( $config, $force_all_checks ) {
26
+ $exs = new Util_Environment_Exceptions();
27
+
28
+ if ( $config->get_boolean( 'config.check' ) || $force_all_checks ) {
29
+ $extensions_active = $config->get_array( 'extensions.active' );
30
+
31
+ if ( array_key_exists( 'imageservice', $extensions_active ) ) {
32
+ $this->rules_add( $config, $exs );
33
+ } else {
34
+ $this->rules_remove( $exs );
35
+ }
36
+ }
37
+
38
+ if ( count( $exs->exceptions() ) > 0 ) {
39
+ throw $exs;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Fixes environment once event occurs.
45
+ *
46
+ * @since 2.2.0
47
+ *
48
+ * @param Config $config Config object.
49
+ * @param mixed $event Event.
50
+ * @param Config $old_config Old config object.
51
+ */
52
+ public function fix_on_event( $config, $event, $old_config = null ) {
53
+ }
54
+
55
+ /**
56
+ * Fixes environment after plugin deactivation
57
+ *
58
+ * @since 2.2.0
59
+ *
60
+ * @throws Util_Environment_Exceptions Exceptions.
61
+ */
62
+ public function fix_after_deactivation() {
63
+ $exs = new Util_Environment_Exceptions();
64
+
65
+ $this->rules_remove( $exs );
66
+
67
+ if ( count( $exs->exceptions() ) > 0 ) {
68
+ throw $exs;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Returns required rules for module.
74
+ *
75
+ * @since 2.2.0
76
+ *
77
+ * @param Config $config Configuration object.
78
+ * @return array
79
+ */
80
+ public function get_required_rules( $config ) {
81
+ return array(
82
+ array(
83
+ 'filename' => Util_Rule::get_browsercache_rules_cache_path(),
84
+ 'content' => $this->rules_generate(),
85
+ ),
86
+ );
87
+ }
88
+
89
+ /**
90
+ * Write rewrite rules.
91
+ *
92
+ * @since 2.2.0
93
+ *
94
+ * @param Config $config Configuration.
95
+ * @param Util_Environment_Exceptions $exs Exceptions.
96
+ *
97
+ * @throws Util_WpFile_FilesystemOperationException S/FTP form if it can't get the required filesystem credentials.
98
+ */
99
+ private function rules_add( $config, $exs ) {
100
+ Util_Rule::add_rules(
101
+ $exs,
102
+ Util_Rule::get_browsercache_rules_cache_path(),
103
+ $this->rules_generate(),
104
+ W3TC_MARKER_BEGIN_WEBP,
105
+ W3TC_MARKER_END_WEBP,
106
+ array(
107
+ W3TC_MARKER_BEGIN_BROWSERCACHE_CACHE => 0,
108
+ W3TC_MARKER_BEGIN_WORDPRESS => 0,
109
+ )
110
+ );
111
+ }
112
+
113
+ /**
114
+ * Generate rewrite rules.
115
+ *
116
+ * @since 2.2.0
117
+ *
118
+ * @see Dispatcher::nginx_rules_for_browsercache_section()
119
+ *
120
+ * @return string
121
+ */
122
+ private function rules_generate() {
123
+ switch ( true ) {
124
+ case Util_Environment::is_apache():
125
+ case Util_Environment::is_litespeed():
126
+ return '
127
+ # BEGIN W3TC WEBP
128
+ <IfModule mod_rewrite.c>
129
+ RewriteEngine On
130
+ RewriteCond %{HTTP_ACCEPT} image/webp
131
+ RewriteCond %{REQUEST_FILENAME} (.+)\.(jpe?g|png|gif)$
132
+ RewriteCond %1\.webp -f
133
+ RewriteCond %{QUERY_STRING} !type=original
134
+ RewriteRule (.+)\.(jpe?g|png|gif)$ $1.webp [NC,T=image/webp,E=webp,L]
135
+ </IfModule>
136
+ <IfModule mod_headers.c>
137
+ <FilesMatch "\.(jpe?g|png|gif)$">
138
+ Header append Vary Accept
139
+ </FilesMatch>
140
+ </IfModule>
141
+ AddType image/webp .webp
142
+ # END W3TC WEBP
143
+
144
+ ';
145
+
146
+ case Util_Environment::is_nginx():
147
+ $config = Dispatcher::config();
148
+
149
+ /*
150
+ * Add Nginx rules only if Browser Cache is disabled.
151
+ * Otherwise, the rules are added in "BrowserCache_Environment_Nginx.php".
152
+ * @see BrowserCache_Environment_Nginx::generate_section()
153
+ */
154
+ if ( ! $config->get_boolean( 'browsercache.enabled' ) ) {
155
+ if ( $config->get_boolean( 'browsercache.no404wp' ) ) {
156
+ $fallback = '=404';
157
+ } else {
158
+ $fallback = '/index.php?$args';
159
+ }
160
+
161
+ return '
162
+ # BEGIN W3TC WEBP
163
+ location ~* ^(?<path>.+)\.(jpe?g|png|gif)$ {
164
+ if ( $http_accept !~* "webp|\*/\*" ) {
165
+ break;
166
+ }
167
+
168
+ ' . implode( "\n ", Dispatcher::nginx_rules_for_browsercache_section( $config, 'other' ) ) . '
169
+
170
+ add_header Vary Accept;
171
+ try_files ${path}.webp $uri ' . $fallback . ';
172
+ }
173
+ # END W3TC WEBP
174
+
175
+ ';
176
+ } else {
177
+ return '';
178
+ }
179
+
180
+ default:
181
+ return '';
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Removes cache directives
187
+ *
188
+ * @since 2.2.0
189
+ *
190
+ * @param Util_Environment_Exceptions $exs Exceptions.
191
+ *
192
+ * @throws Util_WpFile_FilesystemOperationException S/FTP form if it can't get the required filesystem credentials.
193
+ */
194
+ private function rules_remove( $exs ) {
195
+ Util_Rule::remove_rules(
196
+ $exs,
197
+ Util_Rule::get_pgcache_rules_core_path(),
198
+ W3TC_MARKER_BEGIN_WEBP,
199
+ W3TC_MARKER_END_WEBP
200
+ );
201
+ }
202
+ }
Extension_ImageService_Page_View.php ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * File: Extension_ImageService_Page_View.php
4
+ *
5
+ * View for the Image Service extension settings, tools, and statistics page.
6
+ *
7
+ * @since 2.2.0
8
+ *
9
+ * @package W3TC
10
+ *
11
+ * @uses Config $c Configuration object.
12
+ * @uses array $counts Image Service media counts.
13
+ * @uses array|false $usage API usage statistics.
14
+ */
15
+
16
+ namespace W3TC;
17
+
18
+ if ( ! defined( 'W3TC' ) ) {
19
+ die();
20
+ }
21
+
22
+ ?>
23
+ <div class="wrap" id="w3tc">
24
+
25
+ <?php
26
+ // Upgrade banner.
27
+ if ( ! Util_Environment::is_w3tc_pro( $c ) ) {
28
+ require W3TC_INC_DIR . '/options/parts/dashboard_banner.php';
29
+ }
30
+ ?>
31
+
32
+ <p>
33
+ Total Cache Image Service is currently
34
+ <?php
35
+ if ( $c->is_extension_active( 'imageservice' ) ) {
36
+ ?>
37
+ <span class="w3tc-enabled">enabled</span>
38
+ <?php
39
+ } else {
40
+ ?>
41
+ <span class="w3tc-disabled">disabled</span>
42
+ <?php
43
+ }
44
+ ?>
45
+ .
46
+ </p>
47
+
48
+ <form id="w3tc-imageservice-settings" action="upload.php?page=w3tc_extension_page_imageservice" method="post">
49
+ <div class="metabox-holder">
50
+
51
+ <?php Util_Ui::postbox_header( esc_html__( 'Configuration', 'w3-total-cache' ), '', '' ); ?>
52
+
53
+ <table class="form-table" id="w3tc-imageservice-config">
54
+ <?php
55
+ Util_Ui::config_item(
56
+ array(
57
+ 'key' => array(
58
+ 'imageservice',
59
+ 'compression',
60
+ ),
61
+ 'label' => esc_html__( 'Compression type:', 'w3-total-cache' ),
62
+ 'control' => 'radiogroup',
63
+ 'radiogroup_values' => array(
64
+ 'lossy' => 'Lossy',
65
+ 'lossless' => 'Lossless',
66
+ ),
67
+ 'description' => esc_html__( 'Image compression type.', 'w3-total-cache' ),
68
+ )
69
+ );
70
+
71
+ Util_Ui::config_item(
72
+ array(
73
+ 'key' => array(
74
+ 'imageservice',
75
+ 'auto',
76
+ ),
77
+ 'label' => esc_html__( 'Auto-convert:', 'w3-total-cache' ),
78
+ 'control' => 'radiogroup',
79
+ 'radiogroup_values' => array(
80
+ 'enabled' => 'Enabled',
81
+ 'disabled' => 'Disabled',
82
+ ),
83
+ 'description' => esc_html__( 'Auto-convert images on upload.', 'w3-total-cache' ),
84
+ )
85
+ );
86
+ ?>
87
+ </table>
88
+
89
+ <?php
90
+ Util_Ui::button_config_save( 'extension_imageservice_configuration' );
91
+ Util_Ui::postbox_footer();
92
+
93
+ Util_Ui::postbox_header( esc_html__( 'Tools', 'w3-total-cache' ), '', '' );
94
+ ?>
95
+
96
+ <table class="form-table" id="w3tc-imageservice-tools">
97
+ <?php
98
+ Util_Ui::config_item(
99
+ array(
100
+ 'key' => null,
101
+ 'label' => esc_html__( 'Convert all images:', 'w3-total-cache' ),
102
+ 'label_class' => 'w3tc-imageservice-all',
103
+ 'control' => 'button',
104
+ 'none_label' => 'Convert All',
105
+ 'description' => esc_html__( 'Convert all images in the media library.', 'w3-total-cache' ),
106
+ )
107
+ );
108
+
109
+ Util_Ui::config_item(
110
+ array(
111
+ 'key' => null,
112
+ 'label' => esc_html__( 'Revert all images:', 'w3-total-cache' ),
113
+ 'label_class' => 'w3tc-imageservice-revertall',
114
+ 'control' => 'button',
115
+ 'none_label' => 'Revert All',
116
+ 'description' => esc_html__( 'Revert all converted images in the media library.', 'w3-total-cache' ),
117
+ )
118
+ );
119
+ ?>
120
+ </table>
121
+
122
+ <?php
123
+
124
+ Util_Ui::postbox_footer();
125
+
126
+ Util_Ui::postbox_header(
127
+ esc_html__( 'Statistics', 'w3-total-cache' ),
128
+ '',
129
+ 'w3tc-imageservice-statistics'
130
+ );
131
+
132
+ ?>
133
+
134
+ <table class="form-table" id="w3tc-imageservice-stats">
135
+ <tr>
136
+ <th><?php esc_html_e( 'Counts and filesizes by status:', 'w3-total-cache' ); ?></th>
137
+ <td>
138
+ <table id="w3tc-imageservice-counts">
139
+ <tr>
140
+ <td><?php esc_html_e( 'Total:', 'w3-total-cache' ); ?></td>
141
+ <td id="w3tc-imageservice-total"><?php echo esc_html( $counts['total'] ); ?></td>
142
+ <td id="w3tc-imageservice-totalbytes"><?php echo esc_html( size_format( $counts['totalbytes'], 2 ) ); ?></td>
143
+ </tr>
144
+ <tr>
145
+ <td><?php esc_html_e( 'Converted:', 'w3-total-cache' ); ?></td>
146
+ <td id="w3tc-imageservice-converted"><?php echo esc_html( $counts['converted'] ); ?></td>
147
+ <td id="w3tc-imageservice-convertedbytes"><?php echo esc_html( size_format( $counts['convertedbytes'], 2 ) ); ?></td>
148
+ </tr>
149
+ <tr>
150
+ <td><?php esc_html_e( 'Sending:', 'w3-total-cache' ); ?></td>
151
+ <td id="w3tc-imageservice-sending"><?php echo esc_html( $counts['sending'] ); ?></td>
152
+ <td id="w3tc-imageservice-sendingbytes"><?php echo esc_html( size_format( $counts['sendingbytes'], 2 ) ); ?></td>
153
+ </tr>
154
+ <tr>
155
+ <td><?php esc_html_e( 'Processing:', 'w3-total-cache' ); ?></td>
156
+ <td id="w3tc-imageservice-processing"><?php echo esc_html( $counts['processing'] ); ?></td>
157
+ <td id="w3tc-imageservice-processingbytes"><?php echo esc_html( size_format( $counts['processingbytes'], 2 ) ); ?></td>
158
+ </tr>
159
+ <tr>
160
+ <td><?php esc_html_e( 'Not converted:', 'w3-total-cache' ); ?></td>
161
+ <td id="w3tc-imageservice-notconverted"><?php echo esc_html( $counts['notconverted'] ); ?></td>
162
+ <td id="w3tc-imageservice-notconvertedbytes"><?php echo esc_html( size_format( $counts['notconvertedbytes'], 2 ) ); ?></td>
163
+ </tr>
164
+ <tr>
165
+ <td><?php esc_html_e( 'Unconverted:', 'w3-total-cache' ); ?></td>
166
+ <td id="w3tc-imageservice-unconverted"><?php echo esc_html( $counts['unconverted'] ); ?></td>
167
+ <td id="w3tc-imageservice-unconvertedbytes"><?php echo esc_html( size_format( $counts['unconvertedbytes'], 2 ) ); ?></td>
168
+ </tr>
169
+ <tr><td height="10"></td></tr>
170
+ <tr>
171
+ <td colspan="3"><input id="w3tc-imageservice-refresh-counts" class="button" type="button" value="<?php esc_attr_e( 'Refresh', 'w3-total-cache' ); ?>" /></td>
172
+ </tr>
173
+ </table>
174
+ </td>
175
+ </tr>
176
+ <tr>
177
+ <th><?php esc_html_e( 'Image Service API usage:', 'w3-total-cache' ); ?></th>
178
+ <td>
179
+ <table id="w3tc-imageservice-usage">
180
+ <tr>
181
+ <td><?php esc_html_e( 'Hourly requests:', 'w3-total-cache' ); ?></td>
182
+ <td id="w3tc-imageservice-usage-hourly"><?php echo esc_html( $usage['usage_hourly'] ); ?></td>
183
+ </tr>
184
+ <tr>
185
+ <td><?php esc_html_e( 'Hourly limit:', 'w3-total-cache' ); ?></td>
186
+ <td id="w3tc-imageservice-limit-hourly"><?php echo esc_html( $usage['limit_hourly'] ); ?></td>
187
+ </tr>
188
+ <tr>
189
+ <td><?php esc_html_e( 'Monthly requests:', 'w3-total-cache' ); ?></td>
190
+ <td id="w3tc-imageservice-usage-monthly"><?php echo esc_html( $usage['usage_monthly'] ); ?></td>
191
+ </tr>
192
+ <tr>
193
+ <td><?php esc_html_e( 'Monthly limit:', 'w3-total-cache' ); ?></td>
194
+ <td id="w3tc-imageservice-limit-monthly"><?php echo esc_html( $usage['limit_monthly'] ); ?></td>
195
+ </tr>
196
+ <tr><td height="10"></td></tr>
197
+ <tr>
198
+ <td colspan="3"><input id="w3tc-imageservice-refresh-usage" class="button" type="button" value="<?php esc_attr_e( 'Refresh', 'w3-total-cache' ); ?>" /></td>
199
+ </tr>
200
+ </table>
201
+ </td>
202
+ </tr>
203
+ </table>
204
+
205
+ <?php Util_Ui::postbox_footer(); ?>
206
+
207
+ </div>
208
+ </form>
209
+
210
+ </div>
Extension_ImageService_Plugin.php ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * File: Extension_ImageService_Plugin.php
4
+ *
5
+ * @since 2.2.0
6
+ *
7
+ * @package W3TC
8
+ *
9
+ * phpcs:disable WordPress.WP.CronInterval
10
+ */
11
+
12
+ namespace W3TC;
13
+
14
+ if ( ! defined( 'W3TC' ) ) {
15
+ die();
16
+ }
17
+
18
+ /**
19
+ * Extension_ImageService_Plugin
20
+ *
21
+ * @since 2.2.0
22
+ */
23
+ class Extension_ImageService_Plugin {
24
+ /**
25
+ * Image Service API object.
26
+ *
27
+ * @since 2.2.0
28
+ *
29
+ * @static
30
+ *
31
+ * @var Extension_ImageService_Api
32
+ */
33
+ public static $api;
34
+
35
+ /**
36
+ * Add hooks.
37
+ *
38
+ * @since 2.2.0
39
+ * @static
40
+ */
41
+ public static function wp_loaded() {
42
+ add_action(
43
+ 'w3tc_extension_load_admin',
44
+ array(
45
+ '\W3TC\Extension_ImageService_Plugin_Admin',
46
+ 'w3tc_extension_load_admin',
47
+ )
48
+ );
49
+
50
+ // Cron event handling.
51
+ require_once __DIR__ . '/Extension_ImageService_Cron.php';
52
+
53
+ add_action(
54
+ 'w3tc_imageservice_cron',
55
+ array(
56
+ '\W3TC\Extension_ImageService_Cron',
57
+ 'run',
58
+ )
59
+ );
60
+
61
+ add_filter(
62
+ 'cron_schedules',
63
+ array(
64
+ '\W3TC\Extension_ImageService_Cron',
65
+ 'add_schedule',
66
+ )
67
+ );
68
+
69
+ Extension_ImageService_Cron::add_cron();
70
+ }
71
+
72
+ /**
73
+ * Get the Image Service API object.
74
+ *
75
+ * @since 2.2.0
76
+ *
77
+ * @return Extension_ImageService_Api
78
+ */
79
+ public static function get_api() {
80
+ if ( is_null( self::$api ) ) {
81
+ require_once __DIR__ . '/Extension_ImageService_Api.php';
82
+ self::$api = new Extension_ImageService_Api();
83
+ }
84
+
85
+ return self::$api;
86
+ }
87
+ }
88
+
89
+ w3tc_add_action(
90
+ 'wp_loaded',
91
+ array(
92
+ '\W3TC\Extension_ImageService_Plugin',
93
+ 'wp_loaded',
94
+ )
95
+ );
Extension_ImageService_Plugin_Admin.css ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ span.w3tc-convert:before {
2
+ content:'\0041';
3
+ display: inline-block;
4
+ font-family: 'w3tc';
5
+ font-size: x-large;
6
+ }
7
+
8
+ th span.w3tc-convert:before,
9
+ th #w3tc-imageservice-settings {
10
+ vertical-align: text-bottom;
11
+ }
12
+
13
+ p span.w3tc-convert:before {
14
+ vertical-align: middle;
15
+ }
16
+
17
+ .w3tc-converted-reduced {
18
+ color: green;
19
+ }
20
+
21
+ .w3tc-disabled {
22
+ cursor: not-allowed;
23
+ opacity: 0.7;
24
+ }
25
+
26
+ .w3tc-disabled > a {
27
+ color: currentColor;
28
+ display: inline-block; /* For IE11 / Ms Edge bug */
29
+ pointer-events: none;
30
+ text-decoration: none;
31
+ }
32
+
33
+ /* Statistics section on the extension settings, tools, and statistics page. */
34
+
35
+ #w3tc-imageservice-counts td,
36
+ #w3tc-imageservice-usage td {
37
+ padding: 1px 10px;
38
+ }
39
+
40
+ #w3tc_default_save_and_flush_extension_imageservice_configuration {
41
+ display: none;
42
+ }
43
+
44
+ @-webkit-keyframes w3tc-animation-rotating {
45
+ from {
46
+ -webkit-transform: rotate(0deg);
47
+ -o-transform: rotate(0deg);
48
+ transform: rotate(0deg);
49
+ }
50
+
51
+ to {
52
+ -webkit-transform: rotate(360deg);
53
+ -o-transform: rotate(360deg);
54
+ transform: rotate(360deg);
55
+ }
56
+ }
57
+
58
+ @keyframes w3tc-animation-rotating {
59
+ from {
60
+ -ms-transform: rotate(0deg);
61
+ -moz-transform: rotate(0deg);
62
+ -webkit-transform: rotate(0deg);
63
+ -o-transform: rotate(0deg);
64
+ transform: rotate(0deg);
65
+ }
66
+
67
+ to {
68
+ -ms-transform: rotate(360deg);
69
+ -moz-transform: rotate(360deg);
70
+ -webkit-transform: rotate(360deg);
71
+ -o-transform: rotate(360deg);
72
+ transform: rotate(360deg);
73
+ }
74
+ }
75
+
76
+ .w3tc-rotating {
77
+ -webkit-animation: w3tc-animation-rotating 2s linear infinite;
78
+ -moz-animation: w3tc-animation-rotating 2s linear infinite;
79
+ -ms-animation: w3tc-animation-rotating 2s linear infinite;
80
+ -o-animation: w3tc-animation-rotating 2s linear infinite;
81
+ animation: w3tc-animation-rotating 2s linear infinite;
82
+ }
83
+
84
+ @-webkit-keyframes w3tc-highlight {
85
+ 0% {
86
+ background: #fefefe;
87
+ }
88
+
89
+ 50% {
90
+ background: gray;
91
+ }
92
+
93
+ to {
94
+ background: #fefefe;
95
+ }
96
+ }
97
+
98
+ @keyframes w3tc-highlight {
99
+ 0% {
100
+ background: #fefefe;
101
+ }
102
+
103
+ 50% {
104
+ background: gray;
105
+ }
106
+
107
+ to {
108
+ background: #fefefe;
109
+ }
110
+ }
111
+
112
+ .w3tc-highlight {
113
+ -webkit-animation: w3tc-highlight 1s linear;
114
+ -o-animation: w3tc-highlight 1s linear;
115
+ animation: w3tc-highlight 1s linear;
116
+ -webkit-animation-iteration-count: 1;
117
+ -o-animation-iteration-count: 1;
118
+ animation-iteration-count: 1;
119
+ }
120
+
121
+ #w3tc_dashboard_banner {
122
+ width: 100%;
123
+ }
124
+
125
+ a:hover {
126
+ cursor: pointer;
127
+ }
128
+
129
+ .media_page_w3tc_extension_page_imageservice #licensing_terms {
130
+ display:none;
131
+ }
Extension_ImageService_Plugin_Admin.js ADDED
@@ -0,0 +1,661 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * File: Extension_ImageService_Plugin_Admin.js
3
+ *
4
+ * JavaScript for the Media Library list page.
5
+ *
6
+ * @since 2.2.0
7
+ *
8
+ * @global w3tcData Localized data.
9
+ */
10
+
11
+ (function( $ ) {
12
+ var isCheckingItems = false,
13
+ $convertLinks = $( 'a.w3tc-convert' ),
14
+ $unconvertLinks = $( '.w3tc-revert > a' ),
15
+ $convertAllButton = $( 'th.w3tc-imageservice-all' ).parent().find( 'td button' ),
16
+ $revertAllButton = $( 'th.w3tc-imageservice-revertall' ).parent().find( 'td button' ),
17
+ $refreshStatsButton = $( 'input#w3tc-imageservice-refresh-counts.button' ),
18
+ $refreshUsageButton = $( 'input#w3tc-imageservice-refresh-usage.button' );
19
+
20
+ /* On page load. */
21
+
22
+ // Start checking items that are in the processing status.
23
+ startCheckItems();
24
+
25
+ // Disable ineligible buttons.
26
+ toggleButtons();
27
+
28
+ /* Events. */
29
+
30
+ // Clicked convert link.
31
+ $convertLinks.on( 'click', convertItem );
32
+
33
+ // Clicked revert link.
34
+ $unconvertLinks.on( 'click', revertItem );
35
+
36
+ // Clicked convert all images button.
37
+ $convertAllButton.on( 'click', convertItems );
38
+
39
+ // Clicked revert all converted images button.
40
+ $revertAllButton.on( 'click', revertItems );
41
+
42
+ // Clicked the refresh icon for statistics counts.
43
+ $refreshStatsButton.on( 'click', refreshStats );
44
+
45
+ // Clicked the refresh icon for API usage statistics.
46
+ $refreshUsageButton.on( 'click', refreshUsage );
47
+
48
+ /* Functions. */
49
+
50
+ /**
51
+ * Toggle buttons based on eligibility.
52
+ *
53
+ * @since 2.2.0
54
+ */
55
+ function toggleButtons() {
56
+ if ( $convertAllButton.length && $( '#w3tc-imageservice-unconverted' ).text() < 1 ) {
57
+ $convertAllButton.prop( 'disabled', true ).prop( 'aria-disabled', 'true' ); // Disable button.
58
+ }
59
+
60
+ if ( $revertAllButton.length && $( '#w3tc-imageservice-converted' ).text() < 1 ) {
61
+ $revertAllButton.prop( 'disabled', true ).prop( 'aria-disabled', 'true' ); // Disable button.
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Start checking items that are in the processing status.
67
+ *
68
+ * @since 2.2.0
69
+ *
70
+ * @see checkItemsProcessing()
71
+ */
72
+ function startCheckItems() {
73
+ if ( isCheckingItems ) {
74
+ return;
75
+ }
76
+
77
+ isCheckingItems= true;
78
+
79
+ // Check status and update every 5 seconds.
80
+ checkitemsInterval = setInterval( checkItemsProcessing, 5000 );
81
+
82
+ // Stop checking after 5 minutes.
83
+ setTimeout(
84
+ function() {
85
+ clearInterval( checkitemsInterval );
86
+ isCheckingItems = false;
87
+ },
88
+ 60 * 5 * 1000
89
+ );
90
+ }
91
+
92
+ /**
93
+ * Check processing items.
94
+ *
95
+ * @since 2.2.0
96
+ *
97
+ * @see checkItemProcessing()
98
+ */
99
+ function checkItemsProcessing() {
100
+ $convertLinks.each( checkItemProcessing );
101
+ }
102
+
103
+ /**
104
+ * Callback: Check processing item.
105
+ *
106
+ * @since 2.2.0
107
+ */
108
+ function checkItemProcessing() {
109
+ var $this = $( this ),
110
+ $itemTd = $this.closest( 'td' );
111
+
112
+ // If marked as processing, then check for status change an update status on screen.
113
+ if ( 'processing' === $this.data( 'status' ) ) {
114
+ $.ajax({
115
+ method: 'POST',
116
+ url: ajaxurl,
117
+ data: {
118
+ _wpnonce: w3tcData.nonces.postmeta,
119
+ action: 'w3tc_imageservice_postmeta',
120
+ post_id: $this.data( 'post-id' )
121
+ }
122
+ })
123
+ .done( function( response ) {
124
+ var infoClass;
125
+
126
+ // Remove any previous optimization information and the revert link.
127
+ $itemTd.find(
128
+ '.w3tc-converted-reduced, .w3tc-converted-increased, .w3tc-notconverted, .w3tc-revert'
129
+ ).remove();
130
+
131
+ // Add optimization information.
132
+ if ( 'notconverted' !== response.data.status && response.data.download && response.data.download["\u0000*\u0000data"] ) {
133
+ infoClass = response.data.download["\u0000*\u0000data"]['x-filesize-reduced'] > 0 ?
134
+ 'w3tc-converted-increased' : 'w3tc-converted-reduced';
135
+
136
+ $itemTd.prepend(
137
+ '<div class="' +
138
+ infoClass +
139
+ '">' +
140
+ sizeFormat( response.data.download["\u0000*\u0000data"]['x-filesize-in'] ) +
141
+ ' &#8594; ' +
142
+ sizeFormat( response.data.download["\u0000*\u0000data"]['x-filesize-out'] ) +
143
+ ' (' +
144
+ response.data.download["\u0000*\u0000data"]['x-filesize-reduced'] +
145
+ ')</div>'
146
+ );
147
+ }
148
+
149
+ if ( 'converted' === response.data.status ) {
150
+ $this
151
+ .text( w3tcData.lang.converted )
152
+ .data( 'status', 'converted' );
153
+
154
+ // Add revert link, if not already present.
155
+ if ( ! $itemTd.find( '.w3tc-revert' ).length ) {
156
+ $itemTd.append(
157
+ '<span class="w3tc-revert"> | <a>' +
158
+ w3tcData.lang.revert +
159
+ '</a></span>'
160
+ );
161
+
162
+ // Update global revert link.
163
+ $( '.w3tc-revert > a' ).unbind().on( 'click', revertItem );
164
+ }
165
+ } else if ( 'notconverted' === response.data.status ) {
166
+ $this.data( 'status', 'notconverted' );
167
+
168
+ $itemTd.prepend(
169
+ '<div class="w3tc-notconverted">' +
170
+ w3tcData.lang.notConvertedDesc +
171
+ '</div>'
172
+ );
173
+
174
+ if ( 'lossless' === w3tcData.settings.compression ) {
175
+ $this
176
+ .text( w3tcData.lang.settings )
177
+ .prop( 'aria-disabled' , 'false')
178
+ .closest( 'span' ).removeClass( 'w3tc-disabled' );
179
+ } else {
180
+ $this.text( w3tcData.lang.notConverted );
181
+ }
182
+ }
183
+ })
184
+ .fail( function() {
185
+ $this
186
+ .text( w3tcData.lang.error )
187
+ .data( 'status', null );
188
+ $itemTd.find( '.w3tc-imageservice-error' ).remove();
189
+ $itemTd.append(
190
+ '<div class="notice notice-error inline w3tc-imageservice-error">' +
191
+ w3tcData.lang.ajaxFail +
192
+ '</div>'
193
+ );
194
+ });
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Refresh statistics/counts.
200
+ *
201
+ * @since 2.2.0
202
+ */
203
+ function refreshStats() {
204
+ var $countsTable = $( 'table#w3tc-imageservice-counts' );
205
+
206
+ // Update the refresh button text.
207
+ $refreshStatsButton
208
+ .val( w3tcData.lang.refreshing )
209
+ .prop( 'disabled', true )
210
+ .prop( 'aria-disabled', 'true' );
211
+
212
+ // Remove any error notices.
213
+ $countsTable.find( '.w3tc-imageservice-error' ).remove();
214
+
215
+ $.ajax({
216
+ method: 'POST',
217
+ url: ajaxurl,
218
+ data: {
219
+ _wpnonce: w3tcData.nonces.submit,
220
+ action: 'w3tc_imageservice_counts'
221
+ }
222
+ })
223
+ .done( function( response ) {
224
+ if ( response.data && response.data.hasOwnProperty( 'total' ) ) {
225
+ [ 'total', 'converted', 'sending', 'processing', 'notconverted', 'unconverted' ].forEach( function( className ) {
226
+ var size,
227
+ $size,
228
+ $count = $countsTable.find( '#w3tc-imageservice-' + className );
229
+ if ( parseInt( $count.text() ) !== response.data[ className ] ) {
230
+ $count.text( response.data[ className ] ).closest( 'tr' ).addClass( 'w3tc-highlight' );
231
+
232
+ className += 'bytes';
233
+ $size = $countsTable.find( '#w3tc-imageservice-' + className );
234
+ size = sizeFormat( response.data[ className ], 2 );
235
+ $size.text( size );
236
+ }
237
+ } );
238
+ }
239
+
240
+ // Update the refresh button text.
241
+ $refreshStatsButton
242
+ .val( w3tcData.lang.refresh )
243
+ .prop( 'disabled', false )
244
+ .prop( 'aria-disabled', 'false' );
245
+
246
+ // Remove highlights.
247
+ setTimeout(
248
+ function() {
249
+ $countsTable.find( '.w3tc-highlight' ).removeClass( 'w3tc-highlight' );
250
+ },
251
+ 1000
252
+ );
253
+ })
254
+ .fail( function() {
255
+ $countsTable.append(
256
+ '<div class="notice notice-error inline w3tc-imageservice-error">' +
257
+ w3tcData.lang.ajaxFail +
258
+ '</div>'
259
+ );
260
+
261
+ // Update the refresh button text.
262
+ $refreshStatsButton
263
+ .val( w3tcData.lang.error )
264
+ .prop( 'disabled', false )
265
+ .prop( 'aria-disabled', 'false' );
266
+ });
267
+ }
268
+
269
+ /**
270
+ * Refresh API usage statistics.
271
+ *
272
+ * @since 2.2.0
273
+ */
274
+ function refreshUsage() {
275
+ var $usageTable = $( 'table#w3tc-imageservice-usage' );
276
+
277
+ // Update the refresh button text.
278
+ $refreshUsageButton
279
+ .val( w3tcData.lang.refreshing )
280
+ .prop( 'disabled', true )
281
+ .prop( 'aria-disabled', 'true' );
282
+
283
+ // Remove any error notices.
284
+ $usageTable.find( '.w3tc-imageservice-error' ).remove();
285
+
286
+ $.ajax({
287
+ method: 'POST',
288
+ url: ajaxurl,
289
+ data: {
290
+ _wpnonce: w3tcData.nonces.submit,
291
+ action: 'w3tc_imageservice_usage'
292
+ }
293
+ })
294
+ .done( function( response ) {
295
+ if ( response.data && response.data.hasOwnProperty( 'usage_hourly' ) ) {
296
+ [ 'usage_hourly', 'usage_monthly', 'limit_hourly', 'limit_monthly' ].forEach( function( keyName ) {
297
+ var className = keyName.replace( '_', '-' ),
298
+ $count = $usageTable.find( '#w3tc-imageservice-' + className );
299
+ if ( $count.text() != response.data[ keyName ] ) {
300
+ $count.text( response.data[ keyName ] ).closest( 'tr' ).addClass( 'w3tc-highlight' );
301
+ }
302
+ });
303
+ }
304
+
305
+ // Update the refresh button text.
306
+ $refreshUsageButton
307
+ .val( w3tcData.lang.refresh )
308
+ .prop( 'disabled', false )
309
+ .prop( 'aria-disabled', 'false' );
310
+
311
+ // Remove highlights.
312
+ setTimeout(
313
+ function() {
314
+ $usageTable.find( '.w3tc-highlight' ).removeClass( 'w3tc-highlight' );
315
+ },
316
+ 1000
317
+ );
318
+ })
319
+ .fail( function() {
320
+ $usageTable.append(
321
+ '<div class="notice notice-error inline w3tc-imageservice-error">' +
322
+ w3tcData.lang.ajaxFail +
323
+ '</div>'
324
+ );
325
+
326
+ // Update the refresh button text.
327
+ $refreshUsageButton
328
+ .val( w3tcData.lang.error )
329
+ .prop( 'disabled', false )
330
+ .prop( 'aria-disabled', 'false' );
331
+ });
332
+ }
333
+
334
+ /**
335
+ * Convert number of bytes largest unit bytes will fit into.
336
+ *
337
+ * Similar to size_format(), but in JavaScript.
338
+ *
339
+ * @since 2.2.0
340
+ *
341
+ * @param int size Size in bytes.
342
+ * @param int decimals Number of decimal places.
343
+ * @return string
344
+ */
345
+ function sizeFormat( size, decimals = 0 ) {
346
+ var units = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ],
347
+ i = 0;
348
+
349
+ while ( size >= 1024 ) {
350
+ size /= 1024;
351
+ ++i;
352
+ }
353
+
354
+ return size.toFixed( decimals ) + ' ' + units[ i ];
355
+ }
356
+
357
+ /* Event callback functions */
358
+
359
+ /**
360
+ * Event callback: Convert an item.
361
+ *
362
+ * @since 2.2.0
363
+ *
364
+ * @param event e Event object.
365
+ */
366
+ function convertItem() {
367
+ var $this = $( this ),
368
+ $itemTd = $this.closest( 'td' );
369
+
370
+ // If the conversion was canceled and the compression setting is "lossless", then go to the settings page.
371
+ if ( 'notconverted' === $this.data( 'status' ) && 'lossless' === w3tcData.settings.compression ) {
372
+ window.location.href = w3tcData.settingsUrl;
373
+ return;
374
+ }
375
+
376
+ $this
377
+ .text( w3tcData.lang.sending )
378
+ .prop( 'aria-disabled' , 'true')
379
+ .closest( 'span' ).addClass( 'w3tc-disabled' );
380
+
381
+ // Remove any previous optimization information, revert link, and error notices.
382
+ $itemTd.find( '.w3tc-converted-reduced, .w3tc-converted-increased, .w3tc-revert, .w3tc-imageservice-error' ).remove();
383
+
384
+ $.ajax({
385
+ method: 'POST',
386
+ url: ajaxurl,
387
+ data: {
388
+ _wpnonce: w3tcData.nonces.submit,
389
+ action: 'w3tc_imageservice_submit',
390
+ post_id: $this.data( 'post-id' )
391
+ }
392
+ })
393
+ .done( function( response ) {
394
+ if ( response.success ) {
395
+ $this
396
+ .text( w3tcData.lang.processing )
397
+ .data( 'status', 'processing' );
398
+
399
+ startCheckItems();
400
+ } else if ( response.data && response.data.hasOwnProperty( 'error' ) ) {
401
+ $this
402
+ .text( w3tcData.lang.error )
403
+ .data( 'status', 'error' );
404
+
405
+ $itemTd.append(
406
+ '<div class="notice notice-error inline">' +
407
+ response.data.error +
408
+ '</div>'
409
+ );
410
+ } else {
411
+ $this
412
+ .text( w3tcData.lang.error )
413
+ .data( 'status', 'error' );
414
+
415
+ $itemTd.append(
416
+ '<div class="notice notice-error inline w3tc-imageservice-error">' +
417
+ w3tcData.lang.apiError +
418
+ '</div>'
419
+ );
420
+ }
421
+ })
422
+ .fail( function( response ) {
423
+ var message,
424
+ rebindBuyClick = false,
425
+ $wrap = $( '.wrap' );
426
+
427
+ $this
428
+ .val( w3tcData.lang.error )
429
+ .data( 'status', 'error' );
430
+
431
+ if (
432
+ response && response.hasOwnProperty( 'responseJSON' ) &&
433
+ response.responseJSON.hasOwnProperty( 'data' ) &&
434
+ response.responseJSON.data.hasOwnProperty( 'message' )
435
+ ) {
436
+ message = response.responseJSON.data.message;
437
+ rebindBuyClick = true;
438
+ } else {
439
+ message = w3tcData.lang.ajaxFail;
440
+ }
441
+
442
+ $itemTd.append(
443
+ '<div class="notice notice-error inline w3tc-imageservice-error">' +
444
+ message +
445
+ '</div>'
446
+ );
447
+
448
+ // Rebind click event handler after adding a new link that may need it.
449
+ if ( rebindBuyClick ) {
450
+ // Ensure overlay.
451
+ if ( 'w3tc' !== $wrap.attr( 'id' ) ) {
452
+ $wrap.attr( 'id', 'w3tc' );
453
+ }
454
+
455
+ // Rebind click event.
456
+ $( '.button-buy-plugin' )
457
+ .off( 'click' )
458
+ .on( 'click', function() {
459
+ if ( ! $( '.w3tc-overlay' ).length ) {
460
+ w3tc_lightbox_upgrade( w3tc_nonce, $( this ).data('src'), null );
461
+ }
462
+ } );
463
+ }
464
+ });
465
+ }
466
+
467
+ /**
468
+ * Event callback: Revert item.
469
+ *
470
+ * @since 2.2.0
471
+ *
472
+ * @param event e Event object.
473
+ */
474
+ function revertItem() {
475
+ var $this = $( this ),
476
+ $itemTd = $this.closest( 'td' ),
477
+ $convertLink = $itemTd.find( 'a.w3tc-convert' );
478
+
479
+ $this
480
+ .text( w3tcData.lang.reverting )
481
+ .prop( 'aria-disabled', 'true' )
482
+ .closest( 'span' ).addClass( 'w3tc-disabled' );
483
+
484
+ $convertLink
485
+ .prop( 'aria-disabled', 'true' )
486
+ .closest( 'span' ).addClass( 'w3tc-disabled' );
487
+
488
+ // Remove error notices.
489
+ $itemTd.find( '.w3tc-imageservice-error' ).remove();
490
+
491
+ $.ajax({
492
+ method: 'POST',
493
+ url: ajaxurl,
494
+ data: {
495
+ _wpnonce: w3tcData.nonces.revert,
496
+ action: 'w3tc_imageservice_revert',
497
+ post_id: $convertLink.data( 'post-id' )
498
+ }
499
+ })
500
+ .done( function( response ) {
501
+ if ( response.success ) {
502
+ $this.closest( 'span' ).remove(); // Remove the revert link.
503
+ $itemTd.find( 'div' ).remove(); // Remove optimization info.
504
+
505
+ $convertLink
506
+ .text( w3tcData.lang.convert )
507
+ .prop( 'aria-disabled', false )
508
+ .data( 'status', null )
509
+ .closest( 'span' ).removeClass( 'w3tc-disabled' );
510
+ } else if ( response.data && response.data.hasOwnProperty( 'error' ) ) {
511
+ $this
512
+ .text( w3tcData.lang.error )
513
+ .data( 'status', 'error' );
514
+
515
+ $itemTd.parent().append(
516
+ '<div class="notice notice-error inline">' +
517
+ response.data.error +
518
+ '</div>'
519
+ );
520
+ } else {
521
+ $this
522
+ .text( w3tcData.lang.error )
523
+ .data( 'status', 'error' );
524
+
525
+ $itemTd.append(
526
+ '<div class="notice notice-error inline w3tc-imageservice-error">' +
527
+ w3tcData.lang.apiError +
528
+ '</div>'
529
+ );
530
+ }
531
+ })
532
+ .fail( function() {
533
+ $this
534
+ .text( w3tcData.lang.error )
535
+ .data( 'status', 'error' );
536
+
537
+ $itemTd.append(
538
+ '<div class="notice notice-error inline w3tc-imageservice-error">' +
539
+ w3tcData.lang.ajaxFail +
540
+ '</div>'
541
+ );
542
+ });
543
+ }
544
+
545
+ /**
546
+ * Event callback: Convert all items.
547
+ *
548
+ * @since 2.2.0
549
+ *
550
+ * @see refreshStats()
551
+ * @see refreshUsage()
552
+ */
553
+ function convertItems() {
554
+ var $this = $( this ),
555
+ $parent = $this.parent();
556
+
557
+ $this
558
+ .text( w3tcData.lang.sending )
559
+ .prop( 'disabled', true )
560
+ .prop( 'aria-disabled', 'true' );
561
+
562
+ // Remove error notices.
563
+ $parent.find( '.w3tc-imageservice-error' ).remove();
564
+
565
+ $.ajax({
566
+ method: 'POST',
567
+ url: ajaxurl,
568
+ data: {
569
+ _wpnonce: w3tcData.nonces.submit,
570
+ action: 'w3tc_imageservice_all'
571
+ }
572
+ })
573
+ .done( function( response ) {
574
+ if ( response.success ) {
575
+ $this.text( w3tcData.lang.processing );
576
+ refreshStats();
577
+ refreshUsage();
578
+ } else if ( response.data && response.data.hasOwnProperty( 'error' ) ) {
579
+ $this.text( w3tcData.lang.error );
580
+ $parent.append(
581
+ '<div class="notice notice-error inline">' +
582
+ response.data.error +
583
+ '</div>'
584
+ );
585
+ } else {
586
+ $this.text( w3tcData.lang.error );
587
+ $parent.append(
588
+ '<div class="notice notice-error inline w3tc-imageservice-error">' +
589
+ w3tcData.lang.apiError +
590
+ '</div>'
591
+ );
592
+ }
593
+ })
594
+ .fail( function() {
595
+ $this.text( w3tcData.lang.error );
596
+ $parent.append(
597
+ '<div class="notice notice-error inline w3tc-imageservice-error">' +
598
+ w3tcData.lang.ajaxFail +
599
+ '</div>'
600
+ );
601
+ });
602
+ }
603
+
604
+ /**
605
+ * Event callback: Revert all items.
606
+ *
607
+ * @since 2.2.0
608
+ *
609
+ * @see refreshStats()
610
+ */
611
+ function revertItems() {
612
+ var $this = $( this );
613
+
614
+ $this.text( w3tcData.lang.reverting )
615
+ .prop( 'disabled', true )
616
+ .prop( 'aria-disabled', 'true' );
617
+
618
+ $.ajax({
619
+ method: 'POST',
620
+ url: ajaxurl,
621
+ data: {
622
+ _wpnonce: w3tcData.nonces.submit,
623
+ action: 'w3tc_imageservice_revertall'
624
+ }
625
+ })
626
+ .done( function( response ) {
627
+ if ( response.success ) {
628
+ $this.text( w3tcData.lang.reverted );
629
+ $convertAllButton
630
+ .prop( 'disabled', false )
631
+ .prop( 'aria-disabled', 'false' );
632
+ refreshStats();
633
+ } else if ( response.data && response.data.hasOwnProperty( 'error' ) ) {
634
+ $this
635
+ .text( w3tcData.lang.error )
636
+ .parent().append(
637
+ '<div class="notice notice-error inline">' +
638
+ response.data.error +
639
+ '</div>'
640
+ );
641
+ } else {
642
+ $this
643
+ .text( w3tcData.lang.error )
644
+ .parent().append(
645
+ '<div class="notice notice-error inline w3tc-imageservice-error">' +
646
+ w3tcData.lang.apiError +
647
+ '</div>'
648
+ );
649
+ }
650
+ })
651
+ .fail( function() {
652
+ $this
653
+ .text( w3tcData.lang.error )
654
+ .parent().append(
655
+ '<div class="notice notice-error inline w3tc-imageservice-error">' +
656
+ w3tcData.lang.ajaxFail +
657
+ '</div>'
658
+ );
659
+ });
660
+ }
661
+ })( jQuery );
Extension_ImageService_Plugin_Admin.php ADDED
@@ -0,0 +1,1327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * File: Extension_ImageService_Plugin_Admin.php
4
+ *
5
+ * @since 2.2.0
6
+ *
7
+ * @package W3TC
8
+ *
9
+ * phpcs:disable Squiz.PHP.EmbeddedPhp.ContentBeforeOpen, Squiz.PHP.EmbeddedPhp.ContentAfterEnd
10
+ */
11
+
12
+ namespace W3TC;
13
+
14
+ /**
15
+ * Class: Extension_ImageService_Plugin_Admin
16
+ *
17
+ * @since 2.2.0
18
+ */
19
+ class Extension_ImageService_Plugin_Admin {
20
+ /**
21
+ * Image MIME types available for optimization.
22
+ *
23
+ * @since 2.2.0
24
+ * @static
25
+ *
26
+ * @var array
27
+ */
28
+ public static $mime_types = array(
29
+ 'gif' => 'image/gif',
30
+ 'jpeg' => 'image/jpeg',
31
+ 'jpg' => 'image/jpg',
32
+ 'png' => 'image/png',
33
+ );
34
+
35
+ /**
36
+ * Configuration.
37
+ *
38
+ * @since 2.2.0
39
+ * @access private
40
+ *
41
+ * @var Config
42
+ */
43
+ private $config;
44
+
45
+ /**
46
+ * Image Service API class object.
47
+ *
48
+ * @since 2.2.0
49
+ * @access private
50
+ *
51
+ * @var Extension_ImageService_API
52
+ */
53
+ private $api;
54
+
55
+ /**
56
+ * Constructor.
57
+ *
58
+ * @since 2.2.0
59
+ */
60
+ public function __construct() {
61
+ $this->config = Dispatcher::config();
62
+ }
63
+
64
+ /**
65
+ * Get extension information.
66
+ *
67
+ * @since 2.2.0
68
+ * @static
69
+ *
70
+ * @global $wp_version WordPress core version.
71
+ *
72
+ * @param array $extensions Extensions.
73
+ * @param array $config Configuration.
74
+ * @return array
75
+ */
76
+ public static function w3tc_extensions( $extensions, $config ) {
77
+ global $wp_version;
78
+
79
+ $description = __(
80
+ 'Adds the ability to convert images in the Media Library to the modern WebP format for better performance.',
81
+ 'w3-total-cache'
82
+ );
83
+
84
+ if ( version_compare( $wp_version, '5.8', '<' ) ) {
85
+ $description .= sprintf(
86
+ // translators: 1: HTML break, 2: WordPress version string, 3: HTML archor open tag, 4: HTML archor close tag.
87
+ __(
88
+ '%1$sThis extension works best in WordPress version 5.8 and higher. You are running WordPress version %2$s. Please %3$supdate now%4$s to benefit from this feature.',
89
+ 'w3-total-cache'
90
+ ),
91
+ '<br />',
92
+ $wp_version,
93
+ '<a href="' . esc_url( admin_url( 'update-core.php' ) ) . '">',
94
+ '</a>'
95
+ );
96
+ }
97
+
98
+ $settings_url = esc_url( Util_Ui::admin_url( 'upload.php?page=w3tc_extension_page_imageservice&w3tc_imageservice_action=dismiss_activation_notice' ) );
99
+ $library_url = esc_url( Util_Ui::admin_url( 'upload.php?mode=list' ) );
100
+
101
+ $extensions['imageservice'] = array(
102
+ 'name' => 'Image Service',
103
+ 'author' => 'BoldGrid',
104
+ 'description' => esc_html( $description ),
105
+ 'author_uri' => 'https://www.boldgrid.com/',
106
+ 'extension_uri' => 'https://www.boldgrid.com/w3-total-cache/',
107
+ 'extension_id' => 'imageservice',
108
+ 'settings_exists' => false,
109
+ 'version' => '1.0',
110
+ 'enabled' => true,
111
+ 'disabled_message' => '',
112
+ 'requirements' => '',
113
+ 'path' => 'w3-total-cache/Extension_ImageService_Plugin.php',
114
+ 'extra_links' => array(
115
+ '<a class="edit" href="' . $settings_url . '">' . esc_html__( 'Settings', 'w3-total-cache' ) . '</a>',
116
+ '<a class="edit" href="' . $library_url . '">' . esc_html__( 'Media Library', 'w3-total-cache' ) . '</a>',
117
+ ),
118
+ 'notice' => sprintf(
119
+ // translators: 1: HTML anchor open tag, 2: HTML anchor close tag, 3: HTML anchor open tag, 4: HTML anchor open tag.
120
+ __(
121
+ 'Total Cache Image Service has been activated. Now, you can %1$sadjust the settings%2$s or go to the %3$sMedia Library%2$s to convert images to WebP. %4$sLearn more%2$s.',
122
+ 'w3-total-cache'
123
+ ),
124
+ '<a class="edit" href="' . $settings_url . '">',
125
+ '</a>',
126
+ '<a class="edit" href="' . $library_url . '">',
127
+ '<a target="_blank" href="' . esc_url(
128
+ 'https://www.boldgrid.com/support/w3-total-cache/image-service/?utm_source=w3tc&utm_medium=activation_notice&utm_campaign=imageservice'
129
+ ) . '">'
130
+ ),
131
+ );
132
+
133
+ return $extensions;
134
+ }
135
+
136
+ /**
137
+ * Load the admin extension.
138
+ *
139
+ * Runs on the "wp_loaded" action.
140
+ *
141
+ * @since 2.2.0
142
+ * @static
143
+ */
144
+ public static function w3tc_extension_load_admin() {
145
+ $o = new Extension_ImageService_Plugin_Admin();
146
+
147
+ // Enqueue scripts.
148
+ add_action( 'admin_enqueue_scripts', array( $o, 'admin_enqueue_scripts' ) );
149
+
150
+ /**
151
+ * Filters the Media list table columns.
152
+ *
153
+ * @since 2.5.0
154
+ *
155
+ * @param string[] $posts_columns An array of columns displayed in the Media list table.
156
+ * @param bool $detached Whether the list table contains media not attached
157
+ * to any posts. Default true.
158
+ */
159
+ add_filter( 'manage_media_columns', array( $o, 'add_media_column' ) );
160
+
161
+ /**
162
+ * Fires for each custom column in the Media list table.
163
+ *
164
+ * Custom columns are registered using the {@see 'manage_media_columns'} filter.
165
+ *
166
+ * @since 2.5.0
167
+ *
168
+ * @param string $column_name Name of the custom column.
169
+ * @param int $post_id Attachment ID.
170
+ */
171
+ add_action( 'manage_media_custom_column', array( $o, 'media_column_row' ), 10, 2 );
172
+
173
+ // AJAX hooks.
174
+ add_action( 'wp_ajax_w3tc_imageservice_submit', array( $o, 'ajax_submit' ) );
175
+ add_action( 'wp_ajax_w3tc_imageservice_postmeta', array( $o, 'ajax_get_postmeta' ) );
176
+ add_action( 'wp_ajax_w3tc_imageservice_revert', array( $o, 'ajax_revert' ) );
177
+ add_action( 'wp_ajax_w3tc_imageservice_all', array( $o, 'ajax_convert_all' ) );
178
+ add_action( 'wp_ajax_w3tc_imageservice_revertall', array( $o, 'ajax_revert_all' ) );
179
+ add_action( 'wp_ajax_w3tc_imageservice_counts', array( $o, 'ajax_get_counts' ) );
180
+ add_action( 'wp_ajax_w3tc_imageservice_usage', array( $o, 'ajax_get_usage' ) );
181
+
182
+ // Admin notices.
183
+ add_action( 'admin_notices', array( $o, 'display_notices' ) );
184
+
185
+ /**
186
+ * Ensure all network sites include WebP support.
187
+ *
188
+ * @link https://make.wordpress.org/core/2021/06/07/wordpress-5-8-adds-webp-support/
189
+ */
190
+ add_filter(
191
+ 'site_option_upload_filetypes',
192
+ function ( $filetypes ) {
193
+ $filetypes = explode( ' ', $filetypes );
194
+ if ( ! in_array( 'webp', $filetypes, true ) ) {
195
+ $filetypes[] = 'webp';
196
+ }
197
+
198
+ return implode( ' ', $filetypes );
199
+ }
200
+ );
201
+
202
+ // Add bulk actions.
203
+ add_filter( 'bulk_actions-upload', array( $o, 'add_bulk_actions' ) );
204
+
205
+ /**
206
+ * Fires when a custom bulk action should be handled.
207
+ *
208
+ * The redirect link should be modified with success or failure feedback
209
+ * from the action to be used to display feedback to the user.
210
+ *
211
+ * The dynamic portion of the hook name, `$screen`, refers to the current screen ID.
212
+ *
213
+ * @since 4.7.0
214
+ *
215
+ * @link https://core.trac.wordpress.org/browser/tags/5.8/src/wp-admin/upload.php#L206
216
+ *
217
+ * @param string $sendback The redirect URL.
218
+ * @param string $doaction The action being taken.
219
+ * @param array $items The items to take the action on. Accepts an array of IDs of posts,
220
+ * comments, terms, links, plugins, attachments, or users.
221
+ */
222
+ add_filter( 'handle_bulk_actions-upload', array( $o, 'handle_bulk_actions' ), 10, 3 );
223
+
224
+ /**
225
+ * Handle auto-optimization on upload.
226
+ *
227
+ * @link https://core.trac.wordpress.org/browser/tags/5.8/src/wp-includes/post.php#L4401
228
+ * @link https://developer.wordpress.org/reference/hooks/add_attachment/
229
+ *
230
+ * Fires once an attachment has been added.
231
+ *
232
+ * @since 2.0.0
233
+ *
234
+ * @param int $post_ID Attachment ID.
235
+ */
236
+ add_action( 'add_attachment', array( $o, 'auto_convert' ) );
237
+
238
+ /**
239
+ * Delete optimizations on parent image delation.
240
+ *
241
+ * @link https://core.trac.wordpress.org/browser/tags/5.8/src/wp-includes/post.php#L6134
242
+ * @link https://developer.wordpress.org/reference/hooks/pre_delete_attachment/
243
+ *
244
+ * Filters whether an attachment deletion should take place.
245
+ *
246
+ * @since 5.5.0
247
+ *
248
+ * @param bool|null $delete Whether to go forward with deletion.
249
+ * @param WP_Post $post Post object.
250
+ * @param bool $force_delete Whether to bypass the Trash.
251
+ */
252
+ add_filter( 'pre_delete_attachment', array( $o, 'cleanup_optimizations' ), 10, 3 );
253
+
254
+ // Add admin menu items.
255
+ add_action( 'admin_menu', array( $o, 'admin_menu' ) );
256
+ }
257
+
258
+ /**
259
+ * Get all images with postmeta key "w3tc_imageservice".
260
+ *
261
+ * @since 2.2.0
262
+ * @static
263
+ *
264
+ * @link https://developer.wordpress.org/reference/classes/wp_query/
265
+ *
266
+ * @return WP_Query
267
+ */
268
+ public static function get_imageservice_attachments() {
269
+ return new \WP_Query(
270
+ array(
271
+ 'post_type' => 'attachment',
272
+ 'post_status' => 'inherit',
273
+ 'post_mime_type' => self::$mime_types,
274
+ 'posts_per_page' => -1,
275
+ 'ignore_sticky_posts' => true,
276
+ 'suppress_filters' => true,
277
+ 'meta_key' => 'w3tc_imageservice', // phpcs:ignore WordPress.DB.SlowDBQuery
278
+ )
279
+ );
280
+ }
281
+
282
+ /**
283
+ * Get all images without postmeta key "w3tc_imageservice".
284
+ *
285
+ * @since 2.2.0
286
+ * @static
287
+ *
288
+ * @link https://developer.wordpress.org/reference/classes/wp_query/
289
+ *
290
+ * @return WP_Query
291
+ */
292
+ public static function get_eligible_attachments() {
293
+ return new \WP_Query(
294
+ array(
295
+ 'post_type' => 'attachment',
296
+ 'post_status' => 'inherit',
297
+ 'post_mime_type' => self::$mime_types,
298
+ 'posts_per_page' => -1,
299
+ 'ignore_sticky_posts' => true,
300
+ 'suppress_filters' => true,
301
+ 'meta_key' => 'w3tc_imageservice', // phpcs:ignore WordPress.DB.SlowDBQuery
302
+ 'meta_compare' => 'NOT EXISTS',
303
+ )
304
+ );
305
+ }
306
+
307
+ /**
308
+ * Get an attachment filesize.
309
+ *
310
+ * @since 2.2.0
311
+ *
312
+ * @global $wp_filesystem
313
+ *
314
+ * @param int $post_id Post id.
315
+ * @return int
316
+ */
317
+ public function get_attachment_filesize( $post_id ) {
318
+ WP_Filesystem();
319
+ global $wp_filesystem;
320
+
321
+ $size = 0;
322
+ $filepath = get_attached_file( $post_id );
323
+
324
+ if ( $wp_filesystem->exists( $filepath ) ) {
325
+ $size = $wp_filesystem->size( $filepath );
326
+ }
327
+
328
+ return $size;
329
+ }
330
+
331
+ /**
332
+ * Get image counts by status.
333
+ *
334
+ * @since 2.2.0
335
+ *
336
+ * @see self::get_imageservice_attachments()
337
+ * @see self::get_eligible_attachments()
338
+ *
339
+ * @return array
340
+ */
341
+ public function get_counts() {
342
+ $unconverted_posts = self::get_eligible_attachments();
343
+ $counts = array(
344
+ 'sending' => 0,
345
+ 'processing' => 0,
346
+ 'converted' => 0,
347
+ 'notconverted' => 0,
348
+ 'unconverted' => $unconverted_posts->post_count,
349
+ 'total' => 0,
350
+ );
351
+ $bytes = array(
352
+ 'sending' => 0,
353
+ 'processing' => 0,
354
+ 'converted' => 0,
355
+ 'notconverted' => 0,
356
+ 'unconverted' => 0,
357
+ 'total' => 0,
358
+ );
359
+ $imageservice_posts = self::get_imageservice_attachments()->posts;
360
+
361
+ foreach ( $imageservice_posts as $post ) {
362
+ $imageservice_data = get_post_meta( $post->ID, 'w3tc_imageservice', true );
363
+ $status = isset( $imageservice_data['status'] ) ? $imageservice_data['status'] : null;
364
+ $filesize_in = isset( $imageservice_data['download']["\0*\0data"]['x-filesize-in'] ) ?
365
+ $imageservice_data['download']["\0*\0data"]['x-filesize-in'] : 0;
366
+ $filesize_out = isset( $imageservice_data['download']["\0*\0data"]['x-filesize-out'] ) ?
367
+ $imageservice_data['download']["\0*\0data"]['x-filesize-out'] : 0;
368
+
369
+ switch ( $status ) {
370
+ case 'sending':
371
+ $size = $this->get_attachment_filesize( $post->ID );
372
+ $counts['sending']++;
373
+ $bytes['sending'] += $size;
374
+ $bytes['total'] += $size;
375
+ break;
376
+ case 'processing':
377
+ $size = $this->get_attachment_filesize( $post->ID );
378
+ $counts['processing']++;
379
+ $bytes['processing'] += $size;
380
+ $bytes['total'] += $size;
381
+ break;
382
+ case 'converted':
383
+ $counts['converted']++;
384
+ $bytes['converted'] += $filesize_in - $filesize_out;
385
+ $bytes['total'] += $filesize_in - $filesize_out;
386
+ break;
387
+ case 'notconverted':
388
+ $size = $this->get_attachment_filesize( $post->ID );
389
+ $counts['notconverted']++;
390
+ $bytes['notconverted'] += $size;
391
+ $bytes['total'] += $size;
392
+ break;
393
+ case 'unconverted':
394
+ $size = $this->get_attachment_filesize( $post->ID );
395
+ $counts['unconverted']++;
396
+ $bytes['unconverted'] += $size;
397
+ $bytes['total'] += $size;
398
+ break;
399
+ default:
400
+ break;
401
+ }
402
+ }
403
+
404
+ foreach ( $unconverted_posts->posts as $post ) {
405
+ $size = $this->get_attachment_filesize( $post->ID );
406
+
407
+ if ( $size ) {
408
+ $bytes['unconverted'] += $size;
409
+ $bytes['total'] += $size;
410
+ }
411
+ }
412
+
413
+ $counts['total'] = array_sum( $counts );
414
+ $counts['totalbytes'] = $bytes['total'];
415
+ $counts['sendingbytes'] = $bytes['sending'];
416
+ $counts['processingbytes'] = $bytes['processing'];
417
+ $counts['convertedbytes'] = $bytes['converted'];
418
+ $counts['notconvertedbytes'] = $bytes['notconverted'];
419
+ $counts['unconvertedbytes'] = $bytes['unconverted'];
420
+
421
+ return $counts;
422
+ }
423
+
424
+ /**
425
+ * Load the extension settings page view.
426
+ *
427
+ * @since 2.2.0
428
+ *
429
+ * @see Extension_ImageService_Plugin::get_api()
430
+ * @see Extension_ImageService_Api::get_usage()
431
+ */
432
+ public function settings_page() {
433
+ $c = $this->config;
434
+ $counts = $this->get_counts();
435
+ $usage = get_transient( 'w3tc_imageservice_usage' );
436
+
437
+ // Delete transient for displaying activation notice.
438
+ delete_transient( 'w3tc_activation_imageservice' );
439
+
440
+ // Save submitted settings.
441
+ if ( isset( $_POST['_wpnonce'], $_POST['imageservice___compression'] ) && wp_verify_nonce( $_POST['_wpnonce'], 'w3tc' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
442
+ $settings = $c->get_array( 'imageservice' );
443
+
444
+ if ( isset( $_POST['imageservice___compression'] ) ) {
445
+ $settings['compression'] = sanitize_key( $_POST['imageservice___compression'] );
446
+ }
447
+
448
+ if ( isset( $_POST['imageservice___auto'] ) ) {
449
+ $settings['auto'] = sanitize_key( $_POST['imageservice___auto'] );
450
+ }
451
+
452
+ $c->set( 'imageservice', $settings );
453
+ $c->save();
454
+
455
+ // Display notice when saving settings.
456
+ ?>
457
+ <div class="notice notice-success is-dismissible">
458
+ <p><?php esc_html_e( 'Settings saved.', 'w3-total-cache' ); ?></p>
459
+ </div>
460
+ <?php
461
+ }
462
+
463
+ // If usage is not stored, then retrieve it from the API.
464
+ if ( empty( $usage ) ) {
465
+ $usage = Extension_ImageService_Plugin::get_api()->get_usage();
466
+ }
467
+
468
+ // Ensure that the monthly limit is represented correctly.
469
+ $usage['limit_monthly'] = $usage['limit_monthly'] ? $usage['limit_monthly'] : __( 'Unlimited', 'w3-total-cache' );
470
+
471
+ require W3TC_DIR . '/Extension_ImageService_Page_View.php';
472
+ }
473
+
474
+ /**
475
+ * Add admin menu items.
476
+ *
477
+ * @since 2.2.0
478
+ */
479
+ public function admin_menu() {
480
+ // Add settings submenu to Media top-level menu.
481
+ add_submenu_page(
482
+ 'upload.php',
483
+ esc_html__( 'Total Cache Image Service', 'w3-total-cache' ),
484
+ esc_html__( 'Total Cache Image Service', 'w3-total-cache' ),
485
+ 'edit_posts',
486
+ 'w3tc_extension_page_imageservice',
487
+ array( $this, 'settings_page' )
488
+ );
489
+ }
490
+
491
+ /**
492
+ * Enqueue scripts and styles for admin pages.
493
+ *
494
+ * Runs on the "admin_enqueue_scripts" action.
495
+ *
496
+ * @since 2.2.0
497
+ */
498
+ public function admin_enqueue_scripts() {
499
+ // Enqueue JavaScript for the Media Library (upload) and extension settings admin pages.
500
+ $is_settings_page = isset( $_GET['page'] ) && 'w3tc_extension_page_imageservice' === $_GET['page']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
501
+ $is_media_page = 'upload' === get_current_screen()->id;
502
+
503
+ if ( $is_settings_page ) {
504
+ wp_enqueue_style( 'w3tc-options' );
505
+ }
506
+
507
+ if ( $is_settings_page || $is_media_page ) {
508
+ wp_localize_script( 'w3tc-lightbox', 'w3tc_nonce', array( wp_create_nonce( 'w3tc' ) ) );
509
+ wp_enqueue_script( 'w3tc-lightbox' );
510
+ wp_enqueue_style( 'w3tc-lightbox' );
511
+
512
+ wp_register_script(
513
+ 'w3tc-imageservice',
514
+ esc_url( plugin_dir_url( __FILE__ ) . 'Extension_ImageService_Plugin_Admin.js' ),
515
+ array( 'jquery' ),
516
+ W3TC_VERSION,
517
+ true
518
+ );
519
+
520
+ wp_localize_script(
521
+ 'w3tc-imageservice',
522
+ 'w3tcData',
523
+ array(
524
+ 'nonces' => array(
525
+ 'submit' => wp_create_nonce( 'w3tc_imageservice_submit' ),
526
+ 'postmeta' => wp_create_nonce( 'w3tc_imageservice_postmeta' ),
527
+ 'revert' => wp_create_nonce( 'w3tc_imageservice_revert' ),
528
+ ),
529
+ 'lang' => array(
530
+ 'convert' => __( 'Convert', 'w3-total_cache' ),
531
+ 'sending' => __( 'Sending...', 'w3-total_cache' ),
532
+ 'processing' => __( 'Processing...', 'w3-total_cache' ),
533
+ 'converted' => __( 'Converted', 'w3-total_cache' ),
534
+ 'notConverted' => __( 'Not converted', 'w3-total_cache' ),
535
+ 'reverting' => __( 'Reverting...', 'w3-total_cache' ),
536
+ 'reverted' => __( 'Reverted', 'w3-total_cache' ),
537
+ 'revert' => __( 'Revert', 'w3-total_cache' ),
538
+ 'error' => __( 'Error', 'w3-total_cache' ),
539
+ 'ajaxFail' => __( 'Failed to retrieve a response. Please reload the page to try again.', 'w3-total_cache' ),
540
+ 'apiError' => __( 'API error. Please reload the page to try again,', 'w3-total_cache' ),
541
+ 'refresh' => __( 'Refresh', 'w3-total_cache' ),
542
+ 'refreshing' => __( 'Refreshing...', 'w3-total_cache' ),
543
+ 'settings' => __( 'Settings', 'w3-total_cache' ),
544
+ 'notConvertedDesc' => sprintf(
545
+ // translators: 1: HTML anchor open tag, 2: HTML anchor close tag.
546
+ __( 'The converted image would be larger than the original; conversion canceled. %1$sLearn more%2$s.', 'w3-total_cache' ),
547
+ '<a target="_blank" href="' . esc_url(
548
+ 'https://www.boldgrid.com/support/w3-total-cache/image-service#conversion-canceled/?utm_source=w3tc&utm_medium=conversion_canceled&utm_campaign=imageservice'
549
+ ) . '">',
550
+ '</a>'
551
+ ),
552
+ ),
553
+ 'settings' => $this->config->get_array( 'imageservice' ),
554
+ 'settingsUrl' => esc_url( Util_Ui::admin_url( 'upload.php?page=w3tc_extension_page_imageservice' ) ),
555
+ )
556
+ );
557
+
558
+ wp_enqueue_script( 'w3tc-imageservice' );
559
+
560
+ wp_enqueue_style(
561
+ 'w3tc-imageservice',
562
+ esc_url( plugin_dir_url( __FILE__ ) . 'Extension_ImageService_Plugin_Admin.css' ),
563
+ array(),
564
+ W3TC_VERSION,
565
+ 'all'
566
+ );
567
+ }
568
+ }
569
+
570
+ /**
571
+ * Add image service controls to the Media Library table in list view.
572
+ *
573
+ * Runs on the "manage_media_columns" filter.
574
+ *
575
+ * @since 2.2.0
576
+ *
577
+ * @param string[] $posts_columns An array of columns displayed in the Media list table.
578
+ * @param bool $detached Whether the list table contains media not attached
579
+ * to any posts. Default true.
580
+ */
581
+ public function add_media_column( $posts_columns, $detached = true ) {
582
+ // Delete transient for displaying activation notice.
583
+ delete_transient( 'w3tc_activation_imageservice' );
584
+
585
+ $posts_columns['imageservice'] = '<span class="w3tc-convert"></span> ' . esc_html__( 'Image Service', 'w3-total-cache' );
586
+
587
+ return $posts_columns;
588
+ }
589
+
590
+ /**
591
+ * Fires for each custom column in the Media list table.
592
+ *
593
+ * Custom columns are registered using the {@see 'manage_media_columns'} filter.
594
+ * Runs on the "manage_media_custom_column" action.
595
+ *
596
+ * @since 2.5.0
597
+ *
598
+ * @see self::remove_optimizations()
599
+ *
600
+ * @link https://developer.wordpress.org/reference/functions/size_format/
601
+ *
602
+ * @param string $column_name Name of the custom column.
603
+ * @param int $post_id Attachment ID.
604
+ */
605
+ public function media_column_row( $column_name, $post_id ) {
606
+ static $settings;
607
+
608
+ if ( 'imageservice' === $column_name ) {
609
+ $post = get_post( $post_id );
610
+ $imageservice_data = get_post_meta( $post_id, 'w3tc_imageservice', true );
611
+
612
+ $settings = isset( $settings ) ? $settings : $this->config->get_array( 'imageservice' );
613
+
614
+ // Display controls and info for eligible images.
615
+ if ( in_array( $post->post_mime_type, self::$mime_types, true ) ) {
616
+ $filepath = get_attached_file( $post_id );
617
+ $status = isset( $imageservice_data['status'] ) ? $imageservice_data['status'] : null;
618
+
619
+ // Check if image still has the converted file. It could have been deleted.
620
+ if ( 'converted' === $status && isset( $imageservice_data['post_child'] ) ) {
621
+ $child_data = get_post_meta( $imageservice_data['post_child'], 'w3tc_imageservice', true );
622
+
623
+ if ( empty( $child_data['is_converted_file'] ) ) {
624
+ $status = null;
625
+ $this->remove_optimizations( $post_id );
626
+ }
627
+ }
628
+
629
+ // If processed, then show information.
630
+ if ( 'converted' === $status ) {
631
+ $converted_percent = isset( $imageservice_data['download']["\0*\0data"]['x-filesize-out-percent'] ) ?
632
+ $imageservice_data['download']["\0*\0data"]['x-filesize-out-percent'] : null;
633
+ $reduced_percent = isset( $imageservice_data['download']["\0*\0data"]['x-filesize-reduced'] ) ?
634
+ $imageservice_data['download']["\0*\0data"]['x-filesize-reduced'] : null;
635
+ $filesize_in = isset( $imageservice_data['download']["\0*\0data"]['x-filesize-in'] ) ?
636
+ $imageservice_data['download']["\0*\0data"]['x-filesize-in'] : null;
637
+ $filesize_out = isset( $imageservice_data['download']["\0*\0data"]['x-filesize-out'] ) ?
638
+ $imageservice_data['download']["\0*\0data"]['x-filesize-out'] : null;
639
+
640
+ if ( $converted_percent ) {
641
+ $converted_class = rtrim( $converted_percent, '%' ) > 100 ? 'w3tc-converted-increased' : 'w3tc-converted-reduced';
642
+ ?>
643
+ <div class="<?php echo esc_attr( $converted_class ); ?>">
644
+ <?php
645
+ printf(
646
+ '%1$s &#8594; %2$s (%3$s)',
647
+ esc_html( size_format( $filesize_in ) ),
648
+ esc_html( size_format( $filesize_out ) ),
649
+ esc_html( $reduced_percent )
650
+ );
651
+ ?>
652
+ </div>
653
+ <?php
654
+ }
655
+ } elseif ( 'notconverted' === $status ) {
656
+ ?>
657
+ <div class="w3tc-notconverted">
658
+ <?php
659
+ printf(
660
+ // translators: 1: HTML anchor open tag, 2: HTML anchor close tag.
661
+ esc_html__( 'The converted image would be larger than the original; conversion canceled. %1$sLearn more%2$s.', 'w3-total_cache' ),
662
+ '<a target="_blank" href="' . esc_url(
663
+ 'https://www.boldgrid.com/support/w3-total-cache/image-service#conversion-canceled/?utm_source=w3tc&utm_medium=conversion_canceled&utm_campaign=imageservice'
664
+ ) . '">',
665
+ '</a>'
666
+ );
667
+ ?>
668
+ </div>
669
+ <?php
670
+ }
671
+
672
+ // Determine classes.
673
+ $link_classes = 'w3tc-convert';
674
+
675
+ switch ( $status ) {
676
+ case 'processing':
677
+ $link_classes .= ' w3tc-convert-processing';
678
+ $disabled_class = 'w3tc-disabled';
679
+ $aria_attr = 'true';
680
+ break;
681
+ case 'converted':
682
+ $disabled_class = 'w3tc-disabled';
683
+ $aria_attr = 'true';
684
+ break;
685
+ default:
686
+ $disabled_class = '';
687
+ $aria_attr = 'false';
688
+ break;
689
+ }
690
+
691
+ // Print action links.
692
+ ?>
693
+ <span class="<?php echo esc_attr( $disabled_class ); ?>">
694
+ <a class="<?php echo esc_attr( $link_classes ); ?>" data-post-id="<?php echo esc_attr( $post_id ); ?>"
695
+ data-status="<?php echo esc_attr( $status ); ?>" aria-disabled="<?php echo esc_attr( $aria_attr ); ?>">
696
+ <?php
697
+ // phpcs:disable Generic.WhiteSpace.ScopeIndent.IncorrectExact
698
+ switch ( $status ) {
699
+ case 'sending':
700
+ esc_html_e( 'Sending...', 'w3-total-cache' );
701
+ break;
702
+ case 'processing':
703
+ esc_html_e( 'Processing...', 'w3-total-cache' );
704
+ break;
705
+ case 'converted':
706
+ esc_html_e( 'Converted', 'w3-total-cache' );
707
+ break;
708
+ case 'notconverted':
709
+ if ( isset( $settings['compression'] ) && 'lossless' === $settings['compression'] ) {
710
+ esc_html_e( 'Settings', 'w3-total-cache' );
711
+ } else {
712
+ esc_html_e( 'Convert', 'w3-total-cache' );
713
+ }
714
+ break;
715
+ default:
716
+ esc_html_e( 'Convert', 'w3-total-cache' );
717
+ break;
718
+ }
719
+ // phpcs:enable Generic.WhiteSpace.ScopeIndent.IncorrectExact
720
+ ?>
721
+ </a>
722
+ </span>
723
+ <?php
724
+
725
+ // If converted, then show revert link.
726
+ if ( 'converted' === $status ) {
727
+ ?>
728
+ <span class="w3tc-revert"> | <a><?php esc_attr_e( 'Revert', 'w3-total-cache' ); ?></a></span>
729
+ <?php
730
+ }
731
+ } elseif ( isset( $imageservice_data['is_converted_file'] ) && $imageservice_data['is_converted_file'] ) {
732
+ // W3TC converted image.
733
+ echo esc_html__( 'Attachment id: ', 'w3-total-cache' ) . esc_html( $post->post_parent );
734
+ }
735
+ }
736
+ }
737
+
738
+ /**
739
+ * Add bulk actions.
740
+ *
741
+ * @since 2.2.0
742
+ *
743
+ * @param array $actions Bulk actions.
744
+ * @return array
745
+ */
746
+ public function add_bulk_actions( array $actions ) {
747
+ $actions['w3tc_imageservice_convert'] = 'W3 Total Convert';
748
+ $actions['w3tc_imageservice_revert'] = 'W3 Total Convert Revert';
749
+
750
+ return $actions;
751
+ }
752
+
753
+ /**
754
+ * Handle bulk actions.
755
+ *
756
+ * @since 2.2.0
757
+ *
758
+ * @see self::submit_images()
759
+ * @see self::revert_optimizations()
760
+ *
761
+ * @link https://developer.wordpress.org/reference/hooks/handle_bulk_actions-screen/
762
+ * @link https://make.wordpress.org/core/2016/10/04/custom-bulk-actions/
763
+ * @link https://core.trac.wordpress.org/browser/tags/5.8/src/wp-admin/upload.php#L206
764
+ *
765
+ * @since WordPress 4.7.0
766
+ *
767
+ * @param string $location The redirect URL.
768
+ * @param string $doaction The action being taken.
769
+ * @param array $post_ids The items to take the action on. Accepts an array of IDs of attachments.
770
+ * @return string
771
+ */
772
+ public function handle_bulk_actions( $location, $doaction, array $post_ids ) {
773
+ // Remove custom query args.
774
+ $location = remove_query_arg( array( 'w3tc_imageservice_submitted', 'w3tc_imageservice_reverted' ), $location );
775
+
776
+ switch ( $doaction ) {
777
+ case 'w3tc_imageservice_convert':
778
+ $stats = $this->submit_images( $post_ids );
779
+
780
+ $location = add_query_arg(
781
+ array(
782
+ 'w3tc_imageservice_submitted' => $stats['submitted'],
783
+ 'w3tc_imageservice_successful' => $stats['successful'],
784
+ 'w3tc_imageservice_skipped' => $stats['skipped'],
785
+ 'w3tc_imageservice_errored' => $stats['errored'],
786
+ 'w3tc_imageservice_invalid' => $stats['invalid'],
787
+ ),
788
+ $location
789
+ );
790
+
791
+ break;
792
+ case 'w3tc_imageservice_revert':
793
+ $this->revert_optimizations( $post_ids );
794
+
795
+ $location = add_query_arg( 'w3tc_imageservice_reverted', 1, $location );
796
+
797
+ break;
798
+ default:
799
+ break;
800
+ }
801
+
802
+ return $location;
803
+ }
804
+
805
+ /**
806
+ * Display bulk action results admin notice.
807
+ *
808
+ * @since 2.2.0
809
+ *
810
+ * @uses $_GET['w3tc_imageservice_submitted'] Number of submittions.
811
+ * @uses $_GET['w3tc_imageservice_successful'] Number of successful submissions.
812
+ * @uses $_GET['w3tc_imageservice_skipped'] Number of skipped submissions.
813
+ * @uses $_GET['w3tc_imageservice_errored'] Number of errored submissions.
814
+ * @uses $_GET['w3tc_imageservice_invalid'] Number of invalid submissions.
815
+ */
816
+ public function display_notices() {
817
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended
818
+ if ( isset( $_GET['w3tc_imageservice_submitted'] ) ) {
819
+ $submitted = intval( $_GET['w3tc_imageservice_submitted'] );
820
+ $successful = isset( $_GET['w3tc_imageservice_successful'] ) ? intval( $_GET['w3tc_imageservice_successful'] ) : 0;
821
+ $skipped = isset( $_GET['w3tc_imageservice_skipped'] ) ? intval( $_GET['w3tc_imageservice_skipped'] ) : 0;
822
+ $errored = isset( $_GET['w3tc_imageservice_errored'] ) ? intval( $_GET['w3tc_imageservice_errored'] ) : 0;
823
+ $invalid = isset( $_GET['w3tc_imageservice_invalid'] ) ? intval( $_GET['w3tc_imageservice_invalid'] ) : 0;
824
+ // phpcs:enable WordPress.Security.NonceVerification.Recommended
825
+
826
+ ?>
827
+ <script>history.pushState( null, '', location.href.split( '?' )[0] );</script>
828
+
829
+ <div class="updated notice notice-success is-dismissible">
830
+ <p>Total Cache Image Service</p>
831
+ <p>
832
+ <?php
833
+
834
+ printf(
835
+ esc_html(
836
+ // translators: 1: Submissions.
837
+ _n(
838
+ 'Submitted %1$u image for processing.',
839
+ 'Submitted %1$u images for processing.',
840
+ $submitted,
841
+ 'w3-total-cache'
842
+ )
843
+ ) . '</p>',
844
+ esc_attr( $submitted )
845
+ );
846
+
847
+ // Print extra stats if debug is on.
848
+ if ( defined( 'W3TC_DEBUG' ) && W3TC_DEBUG ) {
849
+ ?>
850
+ <p>
851
+ <?php
852
+
853
+ printf(
854
+ // translators: 1: Successes, 2: Skipped, 3: Errored, 4: Invalid.
855
+ esc_html__(
856
+ 'Successful: %1$u | Skipped: %2$u | Errored: %3$u | Invalid: %4$u',
857
+ 'w3-total-cache'
858
+ ),
859
+ esc_attr( $successful ),
860
+ esc_attr( $skipped ),
861
+ esc_attr( $errored ),
862
+ esc_attr( $invalid )
863
+ );
864
+ }
865
+
866
+ ?>
867
+ </p>
868
+ </div>
869
+ <?php
870
+
871
+ } elseif ( isset( $_GET['w3tc_imageservice_reverted'] ) ) { // phpcs:ignore
872
+ ?>
873
+ <script>history.pushState( null, '', location.href.split( '?' )[0] );</script>
874
+
875
+ <div class="updated notice notice-success is-dismissible"><p>Total Cache Image Service</p>
876
+ <p><?php esc_html_e( 'All selected optimizations have been reverted.', 'w3-total-cache' ); ?></p>
877
+ </div>
878
+ <?php
879
+ } elseif ( 'upload' === get_current_screen()->id ) {
880
+ // Media Library: Get the display mode.
881
+ $mode = get_user_option( 'media_library_mode', get_current_user_id() ) ?
882
+ get_user_option( 'media_library_mode', get_current_user_id() ) : 'grid';
883
+
884
+ // If not in list mode, then print a notice to switch to it.
885
+ if ( 'list' !== $mode ) {
886
+ ?>
887
+ <div class="notice notice-warning is-dismissible"><p>Total Cache Image Service -
888
+ <?php
889
+ printf(
890
+ // translators: 1: HTML anchor open tag, 2: HTML anchor close tag.
891
+ esc_html__( 'Switch to %1$slist mode%2$s for WebP conversions.', 'w3-total-cache' ),
892
+ '<a href="' . esc_attr( Util_Ui::admin_url( 'upload.php?mode=list' ) ) . '">',
893
+ '</a>'
894
+ );
895
+ ?>
896
+ </p>
897
+ </div>
898
+ <?php
899
+ }
900
+ }
901
+ }
902
+
903
+ /**
904
+ * Submit images to the API for processing.
905
+ *
906
+ * @since 2.2.0
907
+ *
908
+ * @global $wp_filesystem
909
+ *
910
+ * @see Extension_ImageService_Plugin::get_api()
911
+ *
912
+ * @param array $post_ids Post ids.
913
+ * @return array
914
+ */
915
+ public function submit_images( array $post_ids ) {
916
+ WP_Filesystem();
917
+ global $wp_filesystem;
918
+
919
+ $stats = array(
920
+ 'skipped' => 0,
921
+ 'submitted' => 0,
922
+ 'successful' => 0,
923
+ 'errored' => 0,
924
+ 'invalid' => 0,
925
+ );
926
+
927
+ foreach ( $post_ids as $post_id ) {
928
+ // Skip silently (do not count) if not an allowed MIME type.
929
+ if ( ! in_array( get_post_mime_type( $post_id ), self::$mime_types, true ) ) {
930
+ continue;
931
+ }
932
+
933
+ $filepath = get_attached_file( $post_id );
934
+
935
+ // Skip if attachment file does not exist.
936
+ if ( ! $wp_filesystem->exists( $filepath ) ) {
937
+ $stats['skipped']++;
938
+ continue;
939
+ }
940
+
941
+ // Submit current image.
942
+ $response = Extension_ImageService_Plugin::get_api()->convert( $filepath );
943
+ $stats['submitted']++;
944
+
945
+ if ( isset( $response['error'] ) ) {
946
+ $stats['errored']++;
947
+ continue;
948
+ }
949
+
950
+ if ( empty( $response['job_id'] ) || empty( $response['signature'] ) ) {
951
+ $stats['invalid']++;
952
+ continue;
953
+ }
954
+
955
+ // Remove old optimizations.
956
+ $this->remove_optimizations( $post_id );
957
+
958
+ // Save the job info.
959
+ self::update_postmeta(
960
+ $post_id,
961
+ array(
962
+ 'status' => 'processing',
963
+ 'processing' => $response,
964
+ )
965
+ );
966
+
967
+ $stats['successful']++;
968
+ }
969
+
970
+ return $stats;
971
+ }
972
+
973
+ /**
974
+ * Revert optimizations of images.
975
+ *
976
+ * @since 2.2.0
977
+ *
978
+ * @param array $post_ids Attachment post ids.
979
+ */
980
+ public function revert_optimizations( array $post_ids ) {
981
+ foreach ( $post_ids as $post_id ) {
982
+ // Skip if not an allowed MIME type.
983
+ if ( ! in_array( get_post_mime_type( $post_id ), self::$mime_types, true ) ) {
984
+ continue;
985
+ }
986
+
987
+ $this->remove_optimizations( $post_id );
988
+ }
989
+ }
990
+
991
+ /**
992
+ * Update postmeta.
993
+ *
994
+ * @since 2.2.0
995
+ * @static
996
+ *
997
+ * @link https://developer.wordpress.org/reference/functions/update_post_meta/
998
+ *
999
+ * @param int $post_id Post id.
1000
+ * @param array $data Postmeta data.
1001
+ * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure or if the value
1002
+ * passed to the function is the same as the one that is already in the database.
1003
+ */
1004
+ public static function update_postmeta( $post_id, array $data ) {
1005
+ $postmeta = (array) get_post_meta( $post_id, 'w3tc_imageservice', true );
1006
+ $postmeta = array_merge( $postmeta, $data );
1007
+
1008
+ return update_post_meta( $post_id, 'w3tc_imageservice', $postmeta );
1009
+ }
1010
+
1011
+ /**
1012
+ * Copy postmeta from one post to another.
1013
+ *
1014
+ * @since 2.2.0
1015
+ * @static
1016
+ *
1017
+ * @link https://developer.wordpress.org/reference/functions/update_post_meta/
1018
+ *
1019
+ * @param int $post_id_1 Post id 1.
1020
+ * @param int $post_id_2 Post id 2.
1021
+ * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure or if the value
1022
+ * passed to the function is the same as the one that is already in the database.
1023
+ */
1024
+ public static function copy_postmeta( $post_id_1, $post_id_2 ) {
1025
+ $postmeta = (array) get_post_meta( $post_id_1, 'w3tc_imageservice', true );
1026
+
1027
+ // Do not copy "post_child".
1028
+ unset( $postmeta['post_child'] );
1029
+
1030
+ return update_post_meta( $post_id_2, 'w3tc_imageservice', $postmeta );
1031
+ }
1032
+
1033
+ /**
1034
+ * Remove optimizations.
1035
+ *
1036
+ * @since 2.2.0
1037
+ *
1038
+ * @link https://developer.wordpress.org/reference/functions/wp_delete_attachment/
1039
+ *
1040
+ * @param int $post_id Parent post id.
1041
+ * @return WP_Post|false|null Post data on success, false or null on failure.
1042
+ */
1043
+ public function remove_optimizations( $post_id ) {
1044
+ $result = null;
1045
+
1046
+ // Get child post id.
1047
+ $postmeta = (array) get_post_meta( $post_id, 'w3tc_imageservice', true );
1048
+ $child_id = isset( $postmeta['post_child'] ) ? $postmeta['post_child'] : null;
1049
+
1050
+ if ( $child_id ) {
1051
+ // Delete optimization.
1052
+ $result = wp_delete_attachment( $child_id, true );
1053
+ }
1054
+
1055
+ // Delete postmeta.
1056
+ delete_post_meta( $post_id, 'w3tc_imageservice' );
1057
+
1058
+ return $result;
1059
+ }
1060
+
1061
+ /**
1062
+ * Handle auto-optimization on image upload.
1063
+ *
1064
+ * @since 2.2.0
1065
+ *
1066
+ * @param int $post_id Post id.
1067
+ */
1068
+ public function auto_convert( $post_id ) {
1069
+ $settings = $this->config->get_array( 'imageservice' );
1070
+ $enabled = isset( $settings['auto'] ) && 'enabled' === $settings['auto'];
1071
+
1072
+ if ( $enabled && in_array( get_post_mime_type( $post_id ), self::$mime_types, true ) ) {
1073
+ $this->submit_images( array( $post_id ) );
1074
+ }
1075
+ }
1076
+
1077
+ /**
1078
+ * Delete optimizations on parent image delation.
1079
+ *
1080
+ * Does not filter the WordPress operation. We use this as an action trigger.
1081
+ *
1082
+ * @since 2.2.0
1083
+ *
1084
+ * @param bool|null $delete Whether to go forward with deletion.
1085
+ * @param WP_Post $post Post object.
1086
+ * @param bool $force_delete Whether to bypass the Trash.
1087
+ * @return null
1088
+ */
1089
+ public function cleanup_optimizations( $delete, $post, $force_delete ) {
1090
+ if ( $force_delete ) {
1091
+ $this->remove_optimizations( $post->ID );
1092
+ }
1093
+
1094
+ return null;
1095
+ }
1096
+
1097
+ /**
1098
+ * AJAX: Submit an image for processing.
1099
+ *
1100
+ * @since 2.2.0
1101
+ *
1102
+ * @global $wp_filesystem
1103
+ *
1104
+ * @see Extension_ImageService_Plugin::get_api()
1105
+ *
1106
+ * @uses $_POST['post_id'] Post id.
1107
+ */
1108
+ public function ajax_submit() {
1109
+ check_ajax_referer( 'w3tc_imageservice_submit' );
1110
+
1111
+ WP_Filesystem();
1112
+ global $wp_filesystem;
1113
+
1114
+ // Check for post id.
1115
+ $post_id = isset( $_POST['post_id'] ) ? (int) sanitize_key( $_POST['post_id'] ) : null;
1116
+
1117
+ if ( ! $post_id ) {
1118
+ wp_send_json_error(
1119
+ array(
1120
+ 'error' => __( 'Missing input post id.', 'w3-total-cache' ),
1121
+ ),
1122
+ 400
1123
+ );
1124
+ }
1125
+
1126
+ // Verify the image file exists.
1127
+ $filepath = get_attached_file( $post_id );
1128
+
1129
+ if ( ! $wp_filesystem->exists( $filepath ) ) {
1130
+ wp_send_json_error(
1131
+ array(
1132
+ 'error' => sprintf(
1133
+ // translators: 1: Image filepath.
1134
+ __( 'File "%1$s" does not exist.', 'w3-total-cache' ),
1135
+ $filepath
1136
+ ),
1137
+ ),
1138
+ 412
1139
+ );
1140
+ }
1141
+
1142
+ // Submit the job request.
1143
+ $response = Extension_ImageService_Plugin::get_api()->convert( $filepath );
1144
+
1145
+ // Check for non-200 status code.
1146
+ if ( isset( $response['code'] ) && 200 !== $response['code'] ) {
1147
+ wp_send_json_error(
1148
+ $response,
1149
+ $response['code']
1150
+ );
1151
+ }
1152
+
1153
+ // Check for error.
1154
+ if ( isset( $response['error'] ) ) {
1155
+ wp_send_json_error(
1156
+ $response,
1157
+ 417
1158
+ );
1159
+ }
1160
+
1161
+ // Check for valid response data.
1162
+ if ( empty( $response['job_id'] ) || empty( $response['signature'] ) ) {
1163
+ wp_send_json_error(
1164
+ array(
1165
+ 'error' => __( 'Invalid API response.', 'w3-total-cache' ),
1166
+ ),
1167
+ 417
1168
+ );
1169
+ }
1170
+
1171
+ // Remove old optimizations.
1172
+ $this->remove_optimizations( $post_id );
1173
+
1174
+ // Save the job info.
1175
+ self::update_postmeta(
1176
+ $post_id,
1177
+ array(
1178
+ 'status' => 'processing',
1179
+ 'processing' => $response,
1180
+ )
1181
+ );
1182
+
1183
+ wp_send_json_success( $response );
1184
+ }
1185
+
1186
+ /**
1187
+ * AJAX: Get the status of an image, from postmeta.
1188
+ *
1189
+ * @since 2.2.0
1190
+ *
1191
+ * @uses $_POST['post_id'] Post id.
1192
+ */
1193
+ public function ajax_get_postmeta() {
1194
+ check_ajax_referer( 'w3tc_imageservice_postmeta' );
1195
+
1196
+ $post_id = isset( $_POST['post_id'] ) ? (int) sanitize_key( $_POST['post_id'] ) : null;
1197
+
1198
+ if ( $post_id ) {
1199
+ wp_send_json_success( (array) get_post_meta( $post_id, 'w3tc_imageservice', true ) );
1200
+ } else {
1201
+ wp_send_json_error(
1202
+ array(
1203
+ 'error' => __( 'Missing input post id.', 'w3-total-cache' ),
1204
+ ),
1205
+ 400
1206
+ );
1207
+ }
1208
+ }
1209
+
1210
+ /**
1211
+ * AJAX: Revert an optimization.
1212
+ *
1213
+ * @since 2.2.0
1214
+ *
1215
+ * @uses $_POST['post_id'] Parent post id.
1216
+ */
1217
+ public function ajax_revert() {
1218
+ check_ajax_referer( 'w3tc_imageservice_revert' );
1219
+
1220
+ $post_id = isset( $_POST['post_id'] ) ? (int) sanitize_key( $_POST['post_id'] ) : null;
1221
+
1222
+ if ( $post_id ) {
1223
+ $result = $this->remove_optimizations( $post_id );
1224
+
1225
+ if ( $result ) {
1226
+ wp_send_json_success( $result );
1227
+ } else {
1228
+ wp_send_json_error(
1229
+ array(
1230
+ 'error' => __( 'Missing converted attachment id.', 'w3-total-cache' ),
1231
+ ),
1232
+ 410
1233
+ );
1234
+ }
1235
+ } else {
1236
+ wp_send_json_error(
1237
+ array(
1238
+ 'error' => __( 'Missing input post id.', 'w3-total-cache' ),
1239
+ ),
1240
+ 400
1241
+ );
1242
+ }
1243
+ }
1244
+
1245
+ /**
1246
+ * AJAX: Convert all images.
1247
+ *
1248
+ * @since 2.2.0
1249
+ *
1250
+ * @see self::get_eligible_attachments()
1251
+ * @see self::submit_images()
1252
+ */
1253
+ public function ajax_convert_all() {
1254
+ check_ajax_referer( 'w3tc_imageservice_submit' );
1255
+
1256
+ $results = $this->get_eligible_attachments();
1257
+
1258
+ $post_ids = array();
1259
+
1260
+ // Allow plenty of time to complete.
1261
+ ignore_user_abort( true );
1262
+ set_time_limit( 0 );
1263
+
1264
+ foreach ( $results->posts as $post ) {
1265
+ $post_ids[] = $post->ID;
1266
+ }
1267
+
1268
+ $stats = $this->submit_images( $post_ids );
1269
+
1270
+ wp_send_json_success( $stats );
1271
+ }
1272
+
1273
+ /**
1274
+ * AJAX: Revert all converted images.
1275
+ *
1276
+ * @since 2.2.0
1277
+ *
1278
+ * @see self::get_imageservice_attachments()
1279
+ * @see self::remove_optimizations()
1280
+ */
1281
+ public function ajax_revert_all() {
1282
+ check_ajax_referer( 'w3tc_imageservice_submit' );
1283
+
1284
+ $results = $this->get_imageservice_attachments();
1285
+
1286
+ $revert_count = 0;
1287
+
1288
+ // Allow plenty of time to complete.
1289
+ ignore_user_abort( true );
1290
+ set_time_limit( 0 );
1291
+
1292
+ foreach ( $results->posts as $post ) {
1293
+ if ( $this->remove_optimizations( $post->ID ) ) {
1294
+ $revert_count++;
1295
+ }
1296
+ }
1297
+
1298
+ wp_send_json_success( array( 'revert_count' => $revert_count ) );
1299
+ }
1300
+
1301
+ /**
1302
+ * AJAX: Get image counts by status.
1303
+ *
1304
+ * @since 2.2.0
1305
+ *
1306
+ * @see get_counts()
1307
+ */
1308
+ public function ajax_get_counts() {
1309
+ check_ajax_referer( 'w3tc_imageservice_submit' );
1310
+
1311
+ wp_send_json_success( $this->get_counts() );
1312
+ }
1313
+
1314
+ /**
1315
+ * AJAX: Get image API usage.
1316
+ *
1317
+ * @since 2.2.0
1318
+ *
1319
+ * @see Extension_ImageService_Plugin::get_api()
1320
+ * @see Extension_ImageService_Api::get_usage()
1321
+ */
1322
+ public function ajax_get_usage() {
1323
+ check_ajax_referer( 'w3tc_imageservice_submit' );
1324
+
1325
+ wp_send_json_success( Extension_ImageService_Plugin::get_api()->get_usage() );
1326
+ }
1327
+ }
Extensions_Plugin_Admin.php CHANGED
@@ -1,150 +1,138 @@
1
  <?php
 
 
 
 
 
 
2
  namespace W3TC;
3
 
4
  /**
5
- * W3 Total Cache ExtensionsAdmin plugin
6
  */
7
  class Extensions_Plugin_Admin {
8
  /**
9
- * Config
 
 
10
  */
11
- private $_config = null;
12
 
13
- function __construct() {
 
 
 
14
  $this->_config = Dispatcher::config();
15
  }
16
 
17
  /**
18
- * Runs plugin
19
  */
20
- function run() {
21
- // attach w3tc-bundled extensions
22
- add_filter( 'w3tc_extensions', array(
23
- '\W3TC\Extension_CloudFlare_Plugin_Admin',
24
- 'w3tc_extensions' ),
25
- 10, 2 );
26
- add_filter( 'w3tc_extensions', array(
27
- '\W3TC\Extension_FeedBurner_Plugin_Admin',
28
- 'w3tc_extensions' ),
29
- 10, 2 );
30
- add_filter( 'w3tc_extensions', array(
31
- '\W3TC\Extension_FragmentCache_Plugin_Admin',
32
- 'w3tc_extensions' ),
33
- 10, 2 );
34
- add_filter( 'w3tc_extensions', array(
35
- '\W3TC\Extension_Genesis_Plugin_Admin',
36
- 'w3tc_extensions' ),
37
- 10, 2 );
38
- add_filter( 'w3tc_extensions_hooks', array(
39
- '\W3TC\Extension_Genesis_Plugin_Admin',
40
- 'w3tc_extensions_hooks' ) );
41
- add_filter( 'w3tc_notes_genesis_theme', array(
42
- '\W3TC\Extension_Genesis_Plugin_Admin',
43
- 'w3tc_notes_genesis_theme' ) );
44
- add_filter( 'w3tc_extensions', array(
45
- '\W3TC\Extension_NewRelic_Plugin_Admin',
46
- 'w3tc_extensions' ),
47
- 10, 2 );
48
- add_filter( 'w3tc_extensions', array(
49
- '\W3TC\Extension_Swarmify_Plugin_Admin',
50
- 'w3tc_extensions' ),
51
- 10, 2 );
52
- add_filter( 'w3tc_extensions', array(
53
- '\W3TC\Extension_WordPressSeo_Plugin_Admin',
54
- 'w3tc_extensions' ),
55
- 10, 2 );
56
- add_filter( 'w3tc_extensions_hooks', array(
57
- '\W3TC\Extension_WordPressSeo_Plugin_Admin',
58
- 'w3tc_extensions_hooks' ) );
59
- add_action( 'w3tc_notes_wordpress_seo', array(
60
- '\W3TC\Extension_WordPressSeo_Plugin_Admin',
61
- 'w3tc_notes_wordpress_seo' ) );
62
- add_filter( 'w3tc_extensions', array(
63
- '\W3TC\Extension_Wpml_Plugin_Admin',
64
- 'w3tc_extensions' ),
65
- 10, 2 );
66
- add_filter( 'w3tc_extensions', array(
67
- '\W3TC\Extension_Amp_Plugin_Admin',
68
- 'w3tc_extensions' ),
69
- 10, 2 );
70
- add_filter( 'w3tc_extensions_hooks', array(
71
- '\W3TC\Extension_Wpml_Plugin_Admin',
72
- 'w3tc_extensions_hooks' ) );
73
- add_action( 'w3tc_notes_wpml', array(
74
- '\W3TC\Extension_Wpml_Plugin_Admin',
75
- 'w3tc_notes_wpml' ) );
76
-
77
  add_action( 'admin_init', array( $this, 'admin_init' ), 1 );
78
- add_filter( 'pre_update_option_active_plugins', array(
79
- $this, 'pre_update_option_active_plugins' ) );
80
  add_filter( 'w3tc_admin_menu', array( $this, 'w3tc_admin_menu' ), 10000 );
81
- add_action( 'w3tc_settings_page-w3tc_extensions',
82
- array( $this, 'w3tc_settings_page_w3tc_extensions' ) );
83
 
84
  if ( Util_Admin::is_w3tc_admin_page() ) {
85
- if ( isset( $_GET['extension'] ) && isset( $_GET['action'] ) ) {
86
- if ( in_array( $_GET['action'], array( 'activate', 'deactivate' ) ) ) {
 
 
87
  add_action( 'init', array( $this, 'change_extension_status' ) );
88
  }
89
- } elseif ( isset( $_POST['checked'] ) ) {
90
  add_action( 'admin_init', array( $this, 'change_extensions_status' ) );
91
  }
92
  }
93
  }
94
 
95
  /**
96
- * Adds menu
97
  *
98
- * @param unknown $menu
99
  * @return array
100
  */
101
  public function w3tc_admin_menu( $menu ) {
102
  $menu['w3tc_extensions'] = array(
103
- 'page_title' => __( 'Extensions', 'w3-total-cache' ),
104
- 'menu_text' => __( 'Extensions', 'w3-total-cache' ),
105
  'visible_always' => false,
106
- 'order' => 1900
107
  );
108
 
109
  return $menu;
110
  }
111
 
112
  /**
113
- * Loads options page and corresponding view
114
  */
115
  public function w3tc_settings_page_w3tc_extensions() {
116
  $o = new Extensions_Page();
117
  $o->render_content();
118
  }
119
 
 
 
 
 
 
 
120
  public function pre_update_option_active_plugins( $o ) {
121
  delete_option( 'w3tc_extensions_hooks' );
122
 
123
  return $o;
124
  }
125
 
 
 
 
126
  public function admin_init() {
127
- // used to load even inactive extensions if they want to
128
- $s = get_option( 'w3tc_extensions_hooks' );
129
- $hooks = @json_decode( $s, true );
130
- if ( !isset( $hooks['next_check_date'] ) ||
 
131
  $hooks['next_check_date'] < time() ) {
132
  $hooks = array(
133
- 'actions' => array(),
134
- 'filters' => array(),
135
- 'next_check_date' => time() + 24 * 60 * 60
136
  );
 
137
  $hooks = apply_filters( 'w3tc_extensions_hooks', $hooks );
138
- update_option( 'w3tc_extensions_hooks', json_encode( $hooks ) );
 
139
  }
140
 
141
  if ( isset( $hooks['actions'] ) ) {
142
  foreach ( $hooks['actions'] as $hook => $actions_to_call ) {
143
  if ( is_array( $actions_to_call ) ) {
144
- add_action( $hook, function() use ( $actions_to_call ) {
145
- foreach ( $actions_to_call as $action )
146
- do_action( $action );
147
- } );
 
 
 
 
148
  }
149
  }
150
  }
@@ -152,58 +140,100 @@ class Extensions_Plugin_Admin {
152
  if ( isset( $hooks['filters'] ) ) {
153
  foreach ( $hooks['filters'] as $hook => $filters_to_call ) {
154
  if ( is_array( $filters_to_call ) ) {
155
- add_filter( $hook, function( $v ) use ( $filters_to_call ) {
156
- foreach ( $filters_to_call as $filter )
157
- $v = apply_filters( $filter, $v );
 
 
 
158
 
159
  return $v;
160
- } );
 
161
  }
162
  }
163
  }
164
  }
165
 
166
  /**
167
- * Alters the active state of multiple extensions
168
  */
169
  public function change_extensions_status() {
 
170
  $extensions = Util_Request::get_array( 'checked' );
171
- $action = Util_Request::get( 'action' );
172
- if ( '-1' == $action )
173
- $action = Util_Request::get( 'action2' ); // dropdown at bottom
174
 
175
- $message = '';
176
- if ( 'activate-selected' == $action ) {
 
 
 
177
  foreach ( $extensions as $extension ) {
178
- if ( Extensions_Util::activate_extension( $extension, $this->_config ) )
179
  $message .= '&activated=' . $extension;
 
180
  }
181
- wp_redirect( Util_Ui::admin_url( sprintf( 'admin.php?page=w3tc_extensions%s', $message ) ) );
182
- } elseif ( 'deactivate-selected' == $action ) {
 
183
  foreach ( $extensions as $extension ) {
184
- if ( Extensions_Util::deactivate_extension( $extension, $this->_config ) )
185
  $message .= '&deactivated=' . $extension;
 
186
  }
187
- wp_redirect( Util_Ui::admin_url( sprintf( 'admin.php?page=w3tc_extensions%s', $message ) ) );
 
188
  } else {
189
- wp_redirect( Util_Ui::admin_url( 'admin.php?page=w3tc_extensions' ) );
 
190
  }
191
  }
192
 
193
  /**
194
- * Alters the active state of an extension
195
  */
196
  public function change_extension_status() {
197
  $action = Util_Request::get_string( 'action' );
198
 
199
- if ( in_array( $action, array( 'activate', 'deactivate' ) ) ) {
200
  $extension = Util_Request::get_string( 'extension' );
201
- if ( 'activate' == $action ) {
 
202
  Extensions_Util::activate_extension( $extension, $this->_config );
203
- wp_redirect( Util_Ui::admin_url( sprintf( 'admin.php?page=w3tc_extensions&activated=%s', $extension ) ) );
204
- } elseif ( 'deactivate' == $action ) {
 
205
  Extensions_Util::deactivate_extension( $extension, $this->_config );
206
- wp_redirect( Util_Ui::admin_url( sprintf( 'admin.php?page=w3tc_extensions&deactivated=%s', $extension ) ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  }
208
  }
209
  }
1
  <?php
2
+ /**
3
+ * File: Extensions_Plugin_Admin.php
4
+ *
5
+ * @package W3TC
6
+ */
7
+
8
  namespace W3TC;
9
 
10
  /**
11
+ * Class: Extensions_Plugin_Admin
12
  */
13
  class Extensions_Plugin_Admin {
14
  /**
15
+ * Config.
16
+ *
17
+ * @var Config
18
  */
19
+ private $_config = null; // phpcs:ignore
20
 
21
+ /**
22
+ * Constructor.
23
+ */
24
+ public function __construct() {
25
  $this->_config = Dispatcher::config();
26
  }
27
 
28
  /**
29
+ * Runs plugin.
30
  */
31
+ public function run() {
32
+ // Attach w3tc-bundled extensions.
33
+ add_filter( 'w3tc_extensions', array( '\W3TC\Extension_CloudFlare_Plugin_Admin', 'w3tc_extensions' ), 10, 2 );
34
+ add_filter( 'w3tc_extensions', array( '\W3TC\Extension_FeedBurner_Plugin_Admin', 'w3tc_extensions' ), 10, 2 );
35
+ add_filter( 'w3tc_extensions', array( '\W3TC\Extension_FragmentCache_Plugin_Admin', 'w3tc_extensions' ), 10, 2 );
36
+ add_filter( 'w3tc_extensions', array( '\W3TC\Extension_Genesis_Plugin_Admin', 'w3tc_extensions' ), 10, 2 );
37
+ add_filter( 'w3tc_extensions_hooks', array( '\W3TC\Extension_Genesis_Plugin_Admin', 'w3tc_extensions_hooks' ) );
38
+ add_filter( 'w3tc_notes_genesis_theme', array( '\W3TC\Extension_Genesis_Plugin_Admin', 'w3tc_notes_genesis_theme' ) );
39
+ add_filter( 'w3tc_extensions', array( '\W3TC\Extension_NewRelic_Plugin_Admin', 'w3tc_extensions' ), 10, 2 );
40
+ add_filter( 'w3tc_extensions', array( '\W3TC\Extension_Swarmify_Plugin_Admin', 'w3tc_extensions' ), 10, 2 );
41
+ add_filter( 'w3tc_extensions', array( '\W3TC\Extension_WordPressSeo_Plugin_Admin', 'w3tc_extensions' ), 10, 2 );
42
+ add_filter( 'w3tc_extensions_hooks', array( '\W3TC\Extension_WordPressSeo_Plugin_Admin', 'w3tc_extensions_hooks' ) );
43
+ add_action( 'w3tc_notes_wordpress_seo', array( '\W3TC\Extension_WordPressSeo_Plugin_Admin', 'w3tc_notes_wordpress_seo' ) );
44
+ add_filter( 'w3tc_extensions', array( '\W3TC\Extension_Wpml_Plugin_Admin', 'w3tc_extensions' ), 10, 2 );
45
+ add_filter( 'w3tc_extensions', array( '\W3TC\Extension_Amp_Plugin_Admin', 'w3tc_extensions' ), 10, 2 );
46
+ add_filter( 'w3tc_extensions_hooks', array( '\W3TC\Extension_Wpml_Plugin_Admin', 'w3tc_extensions_hooks' ) );
47
+ add_action( 'w3tc_notes_wpml', array( '\W3TC\Extension_Wpml_Plugin_Admin', 'w3tc_notes_wpml' ) );
48
+ add_filter( 'w3tc_extensions', array( '\W3TC\Extension_ImageService_Plugin_Admin', 'w3tc_extensions' ), 10, 2 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  add_action( 'admin_init', array( $this, 'admin_init' ), 1 );
50
+ add_filter( 'pre_update_option_active_plugins', array( $this, 'pre_update_option_active_plugins' ) );
 
51
  add_filter( 'w3tc_admin_menu', array( $this, 'w3tc_admin_menu' ), 10000 );
52
+ add_action( 'w3tc_settings_page-w3tc_extensions', array( $this, 'w3tc_settings_page_w3tc_extensions' ) );
 
53
 
54
  if ( Util_Admin::is_w3tc_admin_page() ) {
55
+ add_action( 'admin_notices', array( $this, 'admin_notices' ) );
56
+
57
+ if ( isset( $_GET['extension'] ) && isset( $_GET['action'] ) ) { // phpcs:ignore
58
+ if ( in_array( $_GET['action'], array( 'activate', 'deactivate' ), true ) ) { // phpcs:ignore
59
  add_action( 'init', array( $this, 'change_extension_status' ) );
60
  }
61
+ } elseif ( isset( $_POST['checked'] ) ) { // phpcs:ignore
62
  add_action( 'admin_init', array( $this, 'change_extensions_status' ) );
63
  }
64
  }
65
  }
66
 
67
  /**
68
+ * Adds menu.
69
  *
70
+ * @param array $menu Menu.
71
  * @return array
72
  */
73
  public function w3tc_admin_menu( $menu ) {
74
  $menu['w3tc_extensions'] = array(
75
+ 'page_title' => __( 'Extensions', 'w3-total-cache' ),
76
+ 'menu_text' => __( 'Extensions', 'w3-total-cache' ),
77
  'visible_always' => false,
78
+ 'order' => 1900,
79
  );
80
 
81
  return $menu;
82
  }
83
 
84
  /**
85
+ * Loads options page and corresponding view.
86
  */
87
  public function w3tc_settings_page_w3tc_extensions() {
88
  $o = new Extensions_Page();
89
  $o->render_content();
90
  }
91
 
92
+ /**
93
+ * Delete hooks option before updating active plugins option.
94
+ *
95
+ * @param mixed $o Option value.
96
+ * @return mixed
97
+ */
98
  public function pre_update_option_active_plugins( $o ) {
99
  delete_option( 'w3tc_extensions_hooks' );
100
 
101
  return $o;
102
  }
103
 
104
+ /**
105
+ * Admin init.
106
+ */
107
  public function admin_init() {
108
+ // Used to load even inactive extensions if they want to.
109
+ $s = get_option( 'w3tc_extensions_hooks' );
110
+ $hooks = @json_decode( $s, true ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
111
+
112
+ if ( ! isset( $hooks['next_check_date'] ) ||
113
  $hooks['next_check_date'] < time() ) {
114
  $hooks = array(
115
+ 'actions' => array(),
116
+ 'filters' => array(),
117
+ 'next_check_date' => time() + 24 * 60 * 60,
118
  );
119
+
120
  $hooks = apply_filters( 'w3tc_extensions_hooks', $hooks );
121
+
122
+ update_option( 'w3tc_extensions_hooks', wp_json_encode( $hooks ) );
123
  }
124
 
125
  if ( isset( $hooks['actions'] ) ) {
126
  foreach ( $hooks['actions'] as $hook => $actions_to_call ) {
127
  if ( is_array( $actions_to_call ) ) {
128
+ add_action(
129
+ $hook,
130
+ function() use ( $actions_to_call ) {
131
+ foreach ( $actions_to_call as $action ) {
132
+ do_action( $action );
133
+ }
134
+ }
135
+ );
136
  }
137
  }
138
  }
140
  if ( isset( $hooks['filters'] ) ) {
141
  foreach ( $hooks['filters'] as $hook => $filters_to_call ) {
142
  if ( is_array( $filters_to_call ) ) {
143
+ add_filter(
144
+ $hook,
145
+ function( $v ) use ( $filters_to_call ) {
146
+ foreach ( $filters_to_call as $filter ) {
147
+ $v = apply_filters( $filter, $v );
148
+ }
149
 
150
  return $v;
151
+ }
152
+ );
153
  }
154
  }
155
  }
156
  }
157
 
158
  /**
159
+ * Alters the active state of multiple extensions.
160
  */
161
  public function change_extensions_status() {
162
+ $message = '';
163
  $extensions = Util_Request::get_array( 'checked' );
164
+ $action = Util_Request::get( 'action' );
 
 
165
 
166
+ if ( '-1' === $action ) {
167
+ $action = Util_Request::get( 'action2' ); // Dropdown at bottom.
168
+ }
169
+
170
+ if ( 'activate-selected' === $action ) {
171
  foreach ( $extensions as $extension ) {
172
+ if ( Extensions_Util::activate_extension( $extension, $this->_config ) ) {
173
  $message .= '&activated=' . $extension;
174
+ }
175
  }
176
+ wp_safe_redirect( Util_Ui::admin_url( sprintf( 'admin.php?page=w3tc_extensions%s', $message ) ) );
177
+ exit;
178
+ } elseif ( 'deactivate-selected' === $action ) {
179
  foreach ( $extensions as $extension ) {
180
+ if ( Extensions_Util::deactivate_extension( $extension, $this->_config ) ) {
181
  $message .= '&deactivated=' . $extension;
182
+ }
183
  }
184
+ wp_safe_redirect( Util_Ui::admin_url( sprintf( 'admin.php?page=w3tc_extensions%s', $message ) ) );
185
+ exit;
186
  } else {
187
+ wp_safe_redirect( Util_Ui::admin_url( 'admin.php?page=w3tc_extensions' ) );
188
+ exit;
189
  }
190
  }
191
 
192
  /**
193
+ * Alters the active state of an extension.
194
  */
195
  public function change_extension_status() {
196
  $action = Util_Request::get_string( 'action' );
197
 
198
+ if ( in_array( $action, array( 'activate', 'deactivate' ), true ) ) {
199
  $extension = Util_Request::get_string( 'extension' );
200
+
201
+ if ( 'activate' === $action ) {
202
  Extensions_Util::activate_extension( $extension, $this->_config );
203
+ wp_safe_redirect( Util_Ui::admin_url( sprintf( 'admin.php?page=w3tc_extensions&activated=%s', $extension ) ) );
204
+ exit;
205
+ } elseif ( 'deactivate' === $action ) {
206
  Extensions_Util::deactivate_extension( $extension, $this->_config );
207
+ wp_safe_redirect( Util_Ui::admin_url( sprintf( 'admin.php?page=w3tc_extensions&deactivated=%s', $extension ) ) );
208
+ exit;
209
+ }
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Display admin notices.
215
+ *
216
+ * @since 2.2.0
217
+ *
218
+ * @see Extensions_Util::get_active_extensions()
219
+ */
220
+ public function admin_notices() {
221
+ $extensions_active = Extensions_Util::get_active_extensions( $this->_config );
222
+
223
+ foreach ( $extensions_active as $id => $info ) {
224
+ $transient_name = 'w3tc_activation_' . $id;
225
+ $action_name = 'w3tc_' . $id . '_action';
226
+
227
+ if ( isset( $_GET[ $action_name ] ) && 'dismiss_activation_notice' === $_GET[ $action_name ] ) { // phpcs:ignore
228
+ delete_transient( $transient_name );
229
+ }
230
+
231
+ if ( isset( $info['notice'] ) && get_transient( $transient_name ) ) {
232
+ ?>
233
+ <div class="notice notice-warning is-dismissible">
234
+ <p><?php echo $info['notice']; //phpcs:ignore ?></p>
235
+ </div>
236
+ <?php
237
  }
238
  }
239
  }
Extensions_Util.php CHANGED
@@ -1,97 +1,127 @@
1
  <?php
 
 
 
 
 
 
2
  namespace W3TC;
3
 
 
 
 
4
  class Extensions_Util {
5
  /**
6
  * Get registered extensions
7
  *
8
- * @param unknown $config
 
 
9
  * @return array
10
  */
11
- static public function get_extensions( $config ) {
12
- return apply_filters( "w3tc_extensions", __return_empty_array(), $config );
13
  }
14
 
15
  /**
16
  * Get registered extension
17
  *
18
- * @param unknown $extension
19
- * @param unknown $config
 
 
20
  * @return array
21
  */
22
- static public function get_extension( $config, $extension ) {
23
  $exts = self::get_extensions( $config );
24
- if ( !isset( $exts[$extension] ) )
 
25
  return null;
 
26
 
27
- return $exts[$extension];
28
  }
29
 
30
  /**
31
  * Returns the inactive extensions
32
  *
33
- * @param unknown $config
 
 
34
  * @return array
35
  */
36
- static public function get_inactive_extensions( $config ) {
37
- $extensions = Extensions_Util::get_extensions( $config );
38
- $config = Dispatcher::config();
39
  $active_extensions = $config->get_array( 'extensions.active' );
 
40
  return array_diff_key( $extensions, $active_extensions );
41
  }
42
 
43
  /**
44
- * Returns the active extensions
 
 
45
  *
46
- * @param unknown $config
47
  * @return array
48
  */
49
- static public function get_active_extensions( $config ) {
50
- $extensions = Extensions_Util::get_extensions( $config );
51
- $extensions_keys = array_keys( $extensions );
52
- $config = Dispatcher::config();
53
  $active_extensions = $config->get_array( 'extensions.active' );
 
54
  return array_intersect_key( $extensions, $active_extensions );
55
  }
56
 
57
  /**
 
58
  *
 
59
  *
60
- * @param unknown $extension
61
- * @param Config $w3_config
 
62
  * @return bool
63
  */
64
- static public function activate_extension( $extension, $w3_config, $dont_save_config = false ) {
65
- $all_extensions = Extensions_Util::get_extensions( $w3_config );
66
- $extensions = $w3_config->get_array( 'extensions.active' );
67
 
68
- if ( !$w3_config->is_extension_active( $extension ) ) {
69
- $meta = $all_extensions[$extension];
70
 
71
  $filename = W3TC_EXTENSION_DIR . '/' . trim( $meta['path'], '/' );
72
- if ( !file_exists( $filename ) )
 
73
  return false;
 
74
 
75
  include $filename;
76
 
77
- $extensions[$extension] = $meta['path'];
 
78
  ksort( $extensions, SORT_STRING );
 
79
  $w3_config->set( 'extensions.active', $extensions );
80
 
81
- // if extensions doesnt want to control frontend activity -
82
- // activate it there too
83
- if ( !isset( $meta['active_frontend_own_control'] ) ||
84
- !$meta['active_frontend_own_control'] ) {
85
  $w3_config->set_extension_active_frontend( $extension, true );
86
  }
87
 
88
-
89
  try {
90
- if ( !$dont_save_config ) {
91
  $w3_config->save();
92
  }
 
 
 
 
93
  return true;
94
  } catch ( \Exception $ex ) {
 
95
  }
96
  }
97
 
@@ -100,17 +130,20 @@ class Extensions_Util {
100
 
101
 
102
  /**
 
103
  *
 
104
  *
105
- * @param unknown $extension
106
- * @param Config $config
107
- * @param bool $dont_save_config
108
  * @return bool
109
  */
110
- static public function deactivate_extension( $extension, $config, $dont_save_config = false ) {
111
  $extensions = $config->get_array( 'extensions.active' );
 
112
  if ( array_key_exists( $extension, $extensions ) ) {
113
- unset( $extensions[$extension] );
114
  ksort( $extensions, SORT_STRING );
115
  $config->set( 'extensions.active', $extensions );
116
  }
@@ -118,11 +151,19 @@ class Extensions_Util {
118
  $config->set_extension_active_frontend( $extension, false );
119
 
120
  try {
121
- if ( !$dont_save_config )
122
  $config->save();
123
- do_action( "w3tc_deactivate_extension_{$extension}" );
 
 
 
 
 
 
124
  return true;
125
- } catch ( \Exception $ex ) {}
 
 
126
 
127
  return false;
128
  }
1
  <?php
2
+ /**
3
+ * File: Extensions_Util.php
4
+ *
5
+ * @package W3TC
6
+ */
7
+
8
  namespace W3TC;
9
 
10
+ /**
11
+ * Class: Extensions_Util
12
+ */
13
  class Extensions_Util {
14
  /**
15
  * Get registered extensions
16
  *
17
+ * @static
18
+ *
19
+ * @param Config $config Configuration object.
20
  * @return array
21
  */
22
+ public static function get_extensions( $config ) {
23
+ return apply_filters( 'w3tc_extensions', __return_empty_array(), $config );
24
  }
25
 
26
  /**
27
  * Get registered extension
28
  *
29
+ * @static
30
+ *
31
+ * @param Config $config Configuration object.
32
+ * @param string $extension Extension.
33
  * @return array
34
  */
35
+ public static function get_extension( $config, $extension ) {
36
  $exts = self::get_extensions( $config );
37
+
38
+ if ( ! isset( $exts[ $extension ] ) ) {
39
  return null;
40
+ }
41
 
42
+ return $exts[ $extension ];
43
  }
44
 
45
  /**
46
  * Returns the inactive extensions
47
  *
48
+ * @static
49
+ *
50
+ * @param Config $config Configuration object.
51
  * @return array
52
  */
53
+ public static function get_inactive_extensions( $config ) {
54
+ $extensions = self::get_extensions( $config );
55
+ $config = Dispatcher::config();
56
  $active_extensions = $config->get_array( 'extensions.active' );
57
+
58
  return array_diff_key( $extensions, $active_extensions );
59
  }
60
 
61
  /**
62
+ * Returns the active extensions.
63
+ *
64
+ * @static
65
  *
66
+ * @param Config $config Configuration object.
67
  * @return array
68
  */
69
+ public static function get_active_extensions( $config ) {
70
+ $extensions = self::get_extensions( $config );
71
+ $extensions_keys = array_keys( $extensions );
72
+ $config = Dispatcher::config();
73
  $active_extensions = $config->get_array( 'extensions.active' );
74
+
75
  return array_intersect_key( $extensions, $active_extensions );
76
  }
77
 
78
  /**
79
+ * Activate extension.
80
  *
81
+ * @static
82
  *
83
+ * @param string $extension Extension.
84
+ * @param Config $w3_config Configuration object.
85
+ * @param bool $dont_save_config Whether or not to save configuration. Default: false.
86
  * @return bool
87
  */
88
+ public static function activate_extension( $extension, $w3_config, $dont_save_config = false ) {
89
+ $all_extensions = self::get_extensions( $w3_config );
90
+ $extensions = $w3_config->get_array( 'extensions.active' );
91
 
92
+ if ( ! $w3_config->is_extension_active( $extension ) ) {
93
+ $meta = $all_extensions[ $extension ];
94
 
95
  $filename = W3TC_EXTENSION_DIR . '/' . trim( $meta['path'], '/' );
96
+
97
+ if ( ! file_exists( $filename ) ) {
98
  return false;
99
+ }
100
 
101
  include $filename;
102
 
103
+ $extensions[ $extension ] = $meta['path'];
104
+
105
  ksort( $extensions, SORT_STRING );
106
+
107
  $w3_config->set( 'extensions.active', $extensions );
108
 
109
+ // if extensions doesnt want to control frontend activity - activate it there too.
110
+ if ( ! isset( $meta['active_frontend_own_control'] ) || ! $meta['active_frontend_own_control'] ) {
 
 
111
  $w3_config->set_extension_active_frontend( $extension, true );
112
  }
113
 
 
114
  try {
115
+ if ( ! $dont_save_config ) {
116
  $w3_config->save();
117
  }
118
+
119
+ // Set transient for displaying activation notice.
120
+ set_transient( 'w3tc_activation_' . $extension, true, DAY_IN_SECONDS );
121
+
122
  return true;
123
  } catch ( \Exception $ex ) {
124
+ return false;
125
  }
126
  }
127
 
130
 
131
 
132
  /**
133
+ * Deactivate extension.
134
  *
135
+ * @static
136
  *
137
+ * @param string $extension Extension.
138
+ * @param Config $config Configuration object.
139
+ * @param bool $dont_save_config Whether or not to save configuration. Default: false.
140
  * @return bool
141
  */
142
+ public static function deactivate_extension( $extension, $config, $dont_save_config = false ) {
143
  $extensions = $config->get_array( 'extensions.active' );
144
+
145
  if ( array_key_exists( $extension, $extensions ) ) {
146
+ unset( $extensions[ $extension ] );
147
  ksort( $extensions, SORT_STRING );
148
  $config->set( 'extensions.active', $extensions );
149
  }
151
  $config->set_extension_active_frontend( $extension, false );
152
 
153
  try {
154
+ if ( ! $dont_save_config ) {
155
  $config->save();
156
+ }
157
+
158
+ // Delete transient for displaying activation notice.
159
+ delete_transient( 'w3tc_activation_' . $extension );
160
+
161
+ do_action( 'w3tc_deactivate_extension_' . $extension );
162
+
163
  return true;
164
+ } catch ( \Exception $ex ) {
165
+ return false;
166
+ }
167
 
168
  return false;
169
  }
FeatureShowcase_Plugin_Admin.php CHANGED
@@ -25,6 +25,17 @@ class FeatureShowcase_Plugin_Admin {
25
  */
26
  private $_page = 'w3tc_feature_showcase'; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
27
 
 
 
 
 
 
 
 
 
 
 
 
28
  /**
29
  * Constructor.
30
  *
@@ -35,17 +46,22 @@ class FeatureShowcase_Plugin_Admin {
35
  * @see self::set_config()
36
  */
37
  public function __construct() {
38
- $page = Util_Request::get_string( 'page' );
 
 
 
 
 
 
39
 
40
- if ( 'w3tc_feature_showcase' === $page ) {
41
- add_action(
42
- 'admin_enqueue_scripts',
43
- array(
44
- $this,
45
- 'enqueue_styles',
46
- )
47
- );
48
- }
49
  }
50
 
51
  /**
@@ -72,8 +88,10 @@ class FeatureShowcase_Plugin_Admin {
72
 
73
  require W3TC_DIR . '/FeatureShowcase_Plugin_Admin_View.php';
74
 
75
- // Mark unseen new features as seen.
76
- $this->mark_seen();
 
 
77
  }
78
 
79
  /**
@@ -82,12 +100,23 @@ class FeatureShowcase_Plugin_Admin {
82
  * @since 2.1.0
83
  */
84
  public function enqueue_styles() {
 
 
85
  wp_enqueue_style(
86
- 'w3tc_feature_showcase',
87
- esc_url( plugin_dir_url( __FILE__ ) . 'pub/css/feature-showcase.css' ),
88
  array(),
89
  W3TC_VERSION
90
  );
 
 
 
 
 
 
 
 
 
91
  }
92
 
93
  /**
@@ -168,10 +197,52 @@ class FeatureShowcase_Plugin_Admin {
168
  * @access private
169
  * @static
170
  *
 
 
171
  * @return array
172
  */
173
  private static function get_cards() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  return array(
 
 
 
 
 
 
 
 
 
 
 
175
  'setup_guide' => array(
176
  'title' => esc_html__( 'Setup Guide Wizard', 'w3-total-cache' ),
177
  'icon' => 'dashicons-superhero',
@@ -194,7 +265,7 @@ class FeatureShowcase_Plugin_Admin {
194
  'link' => '<a target="_blank" href="' . esc_url( 'https://www.boldgrid.com/support/w3-total-cache/lazy-load-google-maps/?utm_source=w3tc&utm_medium=feature_showcase&utm_campaign=pro_lazyload_googlemaps' ) .
195
  '">' . __( 'More info', 'w3-total-cache' ) . '<span class="dashicons dashicons-external"></span></a>',
196
  'is_premium' => true,
197
- 'is_new' => true,
198
  ),
199
  'cdn_fsd' => array(
200
  'title' => esc_html__( 'Full Site Delivery via CDN', 'w3-total-cache' ),
25
  */
26
  private $_page = 'w3tc_feature_showcase'; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
27
 
28
+ /**
29
+ * Location of any wp_redirect.
30
+ *
31
+ * @since 2.2.0
32
+ * @access private
33
+ * @static
34
+ *
35
+ * @var string
36
+ */
37
+ private static $wp_redirect_location;
38
+
39
  /**
40
  * Constructor.
41
  *
46
  * @see self::set_config()
47
  */
48
  public function __construct() {
49
+ add_action(
50
+ 'admin_enqueue_scripts',
51
+ array(
52
+ $this,
53
+ 'enqueue_styles',
54
+ )
55
+ );
56
 
57
+ // Check if being redirected.
58
+ add_filter(
59
+ 'wp_redirect',
60
+ function( $location ) {
61
+ FeatureShowcase_Plugin_Admin::$wp_redirect_location = $location;
62
+ return $location;
63
+ }
64
+ );
 
65
  }
66
 
67
  /**
88
 
89
  require W3TC_DIR . '/FeatureShowcase_Plugin_Admin_View.php';
90
 
91
+ // Mark unseen new features as seen, if not redirecting to the Setup Guide wizard.
92
+ if ( ! self::$wp_redirect_location ) {
93
+ $this->mark_seen();
94
+ }
95
  }
96
 
97
  /**
100
  * @since 2.1.0
101
  */
102
  public function enqueue_styles() {
103
+ $page = Util_Request::get_string( 'page' );
104
+
105
  wp_enqueue_style(
106
+ 'w3tc_feature_counter',
107
+ esc_url( plugin_dir_url( __FILE__ ) . 'pub/css/feature-counter.css' ),
108
  array(),
109
  W3TC_VERSION
110
  );
111
+
112
+ if ( 'w3tc_feature_showcase' === $page ) {
113
+ wp_enqueue_style(
114
+ 'w3tc_feature_showcase',
115
+ esc_url( plugin_dir_url( __FILE__ ) . 'pub/css/feature-showcase.css' ),
116
+ array(),
117
+ W3TC_VERSION
118
+ );
119
+ }
120
  }
121
 
122
  /**
197
  * @access private
198
  * @static
199
  *
200
+ * @global $wp_version WordPress core version.
201
+ *
202
  * @return array
203
  */
204
  private static function get_cards() {
205
+ $c = Dispatcher::config();
206
+ $extensions = $c->get_array( 'extensions.active' );
207
+ $is_imageservice_active = isset( $extensions['imageservice'] );
208
+ $imageservice_button_text = $is_imageservice_active ? __( 'Settings', 'w3-total-cache' ) : __( 'Activate', 'w3-total-cache' );
209
+ $imageservice_button_link = $is_imageservice_active ?
210
+ 'upload.php?page=w3tc_extension_page_imageservice' : 'admin.php?page=w3tc_extensions&action=activate&extension=imageservice';
211
+
212
+ global $wp_version;
213
+
214
+ $imageservice_description = __(
215
+ 'Adds the ability to convert images into the modern WebP format for better performance using our remote API service.',
216
+ 'w3-total-cache'
217
+ );
218
+
219
+ if ( version_compare( $wp_version, '5.8', '<' ) ) {
220
+ $imageservice_description .= sprintf(
221
+ // translators: 1: HTML p open tag, 2: WordPress version string, 3: HTML anchor open tag, 4: HTML anchor close tag, 5: HTML p close tag.
222
+ __(
223
+ '%1$sThis feature works best in WordPress version 5.8 and higher. You are running WordPress version %2$s. Please %3$supdate now%4$s to benefit from this feature.%5$s',
224
+ 'w3-total-cache'
225
+ ),
226
+ '<p>',
227
+ $wp_version,
228
+ '<a href="' . esc_url( admin_url( 'update-core.php' ) ) . '">',
229
+ '</a>',
230
+ '</p>'
231
+ );
232
+ }
233
+
234
  return array(
235
+ 'imageservice' => array(
236
+ 'title' => esc_html__( 'Image Service', 'w3-total-cache' ),
237
+ 'icon' => 'dashicons-embed-photo',
238
+ 'text' => esc_html( $imageservice_description ),
239
+ 'button' => '<button class="button" onclick="window.location=\'' .
240
+ esc_url( Util_Ui::admin_url( $imageservice_button_link ) ) . '\'">' . esc_html( $imageservice_button_text ) . '</button>',
241
+ 'link' => '<a target="_blank" href="' . esc_url( 'https://www.boldgrid.com/support/w3-total-cache/image-service/?utm_source=w3tc&utm_medium=feature_showcase&utm_campaign=imageservice' ) .
242
+ '">' . __( 'More info', 'w3-total-cache' ) . '<span class="dashicons dashicons-external"></span></a>',
243
+ 'is_premium' => false,
244
+ 'is_new' => true,
245
+ ),
246
  'setup_guide' => array(
247
  'title' => esc_html__( 'Setup Guide Wizard', 'w3-total-cache' ),
248
  'icon' => 'dashicons-superhero',
265
  'link' => '<a target="_blank" href="' . esc_url( 'https://www.boldgrid.com/support/w3-total-cache/lazy-load-google-maps/?utm_source=w3tc&utm_medium=feature_showcase&utm_campaign=pro_lazyload_googlemaps' ) .
266
  '">' . __( 'More info', 'w3-total-cache' ) . '<span class="dashicons dashicons-external"></span></a>',
267
  'is_premium' => true,
268
+ 'is_new' => false,
269
  ),
270
  'cdn_fsd' => array(
271
  'title' => esc_html__( 'Full Site Delivery via CDN', 'w3-total-cache' ),
Generic_Page_Dashboard_View.css CHANGED
@@ -14,9 +14,6 @@
14
  -webkit-box-shadow: none;
15
  box-shadow: none;
16
  }
17
- #w3tc-dashboard-widgets h1 {
18
- margin-top: 5px;
19
- }
20
  #w3tc-dashboard-widgets #postbox-container-left {
21
  padding: 10px;
22
  margin-right: 200px;
14
  -webkit-box-shadow: none;
15
  box-shadow: none;
16
  }
 
 
 
17
  #w3tc-dashboard-widgets #postbox-container-left {
18
  padding: 10px;
19
  margin-right: 200px;
Generic_Plugin_Admin.php CHANGED
@@ -266,7 +266,7 @@ class Generic_Plugin_Admin {
266
 
267
  // Define icon styles for the custom post type
268
  function admin_head() {
269
- $page = isset( $_GET['page'] ) ? $_GET['page'] : null;
270
 
271
  if ( ( ! is_multisite() || is_super_admin() ) && false !== strpos( $page, 'w3tc' ) && 'w3tc_setup_guide' !== $page && ! get_site_option( 'w3tc_setupguide_completed' ) ) {
272
  $config = new Config();
266
 
267
  // Define icon styles for the custom post type
268
  function admin_head() {
269
+ $page = Util_Request::get_string( 'page', null );
270
 
271
  if ( ( ! is_multisite() || is_super_admin() ) && false !== strpos( $page, 'w3tc' ) && 'w3tc_setup_guide' !== $page && ! get_site_option( 'w3tc_setupguide_completed' ) ) {
272
  $config = new Config();
Root_AdminActivation.php CHANGED
@@ -100,5 +100,9 @@ class Root_AdminActivation {
100
  Util_Activation::disable_maintenance_mode();
101
  } catch ( \Exception $ex ) {
102
  }
 
 
 
 
103
  }
104
  }
100
  Util_Activation::disable_maintenance_mode();
101
  } catch ( \Exception $ex ) {
102
  }
103
+
104
+ // Delete cron events.
105
+ require_once __DIR__ . '/Extension_ImageService_Cron.php';
106
+ Extension_ImageService_Cron::delete_cron();
107
  }
108
  }
Root_Environment.php CHANGED
@@ -172,7 +172,8 @@ class Root_Environment {
172
  new BrowserCache_Environment(),
173
  new ObjectCache_Environment(),
174
  new DbCache_Environment(),
175
- new Cdn_Environment()
 
176
  );
177
 
178
  return $a;
172
  new BrowserCache_Environment(),
173
  new ObjectCache_Environment(),
174
  new DbCache_Environment(),
175
+ new Cdn_Environment(),
176
+ new Extension_ImageService_Environment(),
177
  );
178
 
179
  return $a;
Root_Loader.php CHANGED
@@ -147,7 +147,6 @@ class Root_Loader {
147
  }
148
 
149
  if ( is_admin() ) {
150
- $extensions = $c->get_array( 'extensions.active' );
151
  foreach ( $extensions as $extension => $path ) {
152
  $filename = W3TC_EXTENSION_DIR . '/' .
153
  str_replace( '..', '', trim( $path, '/' ) );
@@ -163,6 +162,53 @@ class Root_Loader {
163
  if ( is_admin() ) {
164
  do_action( 'w3tc_extension_load_admin' );
165
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  }
167
  }
168
 
147
  }
148
 
149
  if ( is_admin() ) {
 
150
  foreach ( $extensions as $extension => $path ) {
151
  $filename = W3TC_EXTENSION_DIR . '/' .
152
  str_replace( '..', '', trim( $path, '/' ) );
162
  if ( is_admin() ) {
163
  do_action( 'w3tc_extension_load_admin' );
164
  }
165
+
166
+ // Hide Image Service media.
167
+ add_action(
168
+ 'pre_get_posts',
169
+ function( $query ) {
170
+ if ( ! is_admin() || ! $query->is_main_query() ) {
171
+ return;
172
+ }
173
+
174
+ $screen = get_current_screen();
175
+
176
+ if ( ! $screen || 'upload' !== $screen->id || 'attachment' !== $screen->post_type ) {
177
+ return;
178
+ }
179
+
180
+ $query->set(
181
+ 'meta_query',
182
+ array(
183
+ array(
184
+ 'key' => 'w3tc_imageservice_file',
185
+ 'compare' => 'NOT EXISTS',
186
+ ),
187
+ )
188
+ );
189
+
190
+ return;
191
+ }
192
+ );
193
+
194
+ add_filter(
195
+ 'ajax_query_attachments_args',
196
+ function( $args ) {
197
+ if ( ! is_admin() ) {
198
+ return;
199
+ }
200
+
201
+ // Modify the query.
202
+ $args['meta_query'] = array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
203
+ array(
204
+ 'key' => 'w3tc_imageservice_file',
205
+ 'compare' => 'NOT EXISTS',
206
+ ),
207
+ );
208
+
209
+ return $args;
210
+ }
211
+ );
212
  }
213
  }
214
 
Util_Ui.php CHANGED
@@ -648,7 +648,9 @@ class Util_Ui {
648
  'disabled' => $a['disabled']
649
  ) );
650
  } elseif ( 'none' === $a['control'] ) {
651
- esc_html_e( $a['none_label'] );
 
 
652
  }
653
  }
654
 
648
  'disabled' => $a['disabled']
649
  ) );
650
  } elseif ( 'none' === $a['control'] ) {
651
+ esc_html_e( $a['none_label'], 'w3-total-cache' );
652
+ } elseif ( 'button' === $a['control'] ) {
653
+ echo '<button type="button" class="button">' . __( $a['none_label'], 'w3-total-cache' ) . '</button>';
654
  }
655
  }
656
 
inc/options/extensions/list.php CHANGED
@@ -73,11 +73,21 @@ $cb_id++;
73
  <strong><?php esc_html_e( $meta['name'] ) ?></strong>
74
  <div class="row-actions-visible">
75
  <?php if ( $config->is_extension_active( $extension ) ):
76
- $extra_links = array();
77
- if ( isset( $meta['settings_exists'] ) && $meta['settings_exists'] )
78
- $extra_links[] = '<a class="edit" href="' . esc_attr( Util_Ui::admin_url( sprintf( 'admin.php?page=w3tc_extensions&extension=%s', $extension ) ) ).'&action=view">'. __( 'Settings' ).'</a>';
 
 
 
 
 
 
 
 
 
79
  $extra_links = apply_filters( "w3tc_extension_plugin_links_{$extension}", $extra_links );
80
  $links = implode( ' | ', $extra_links );
 
81
  if ( $links ) {
82
  echo $links;
83
  }
73
  <strong><?php esc_html_e( $meta['name'] ) ?></strong>
74
  <div class="row-actions-visible">
75
  <?php if ( $config->is_extension_active( $extension ) ):
76
+ $extra_links = array();
77
+
78
+ if ( isset( $meta['settings_exists'] ) && $meta['settings_exists'] ) {
79
+ $extra_links[] = '<a class="edit" href="' .
80
+ esc_attr( Util_Ui::admin_url( sprintf( 'admin.php?page=w3tc_extensions&extension=%s&action=view', $extension ) ) ) . '">' .
81
+ esc_html__( 'Settings', 'w3-total-cache' ) . '</a>';
82
+ }
83
+
84
+ if ( isset( $meta['extra_links'] ) && is_Array( $meta['extra_links'] ) ) {
85
+ $extra_links = array_merge( $extra_links, $meta['extra_links'] );
86
+ }
87
+
88
  $extra_links = apply_filters( "w3tc_extension_plugin_links_{$extension}", $extra_links );
89
  $links = implode( ' | ', $extra_links );
90
+
91
  if ( $links ) {
92
  echo $links;
93
  }
pub/css/feature-counter.css ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ #wp-admin-bar-w3tc > a .awaiting-mod,
2
+ #wp-admin-bar-w3tc_feature_showcase a .awaiting-mod {
3
+ background-color: #d63638;
4
+ border-radius: 9px;
5
+ color: #fff;
6
+ font-size: 11px;
7
+ height: 18px;
8
+ min-width: 18px;
9
+ padding: 0 5px;
10
+ }
pub/js/feature-counter.js CHANGED
@@ -9,7 +9,8 @@
9
  */
10
 
11
  jQuery(function() {
12
- var $adminmenuItem = jQuery ( '#wp-admin-bar-w3tc_feature_showcase a' ),
 
13
  $menuItem = jQuery( '#toplevel_page_w3tc_dashboard.wp-not-current-submenu a[href="admin.php?page=w3tc_dashboard"] .wp-menu-name' ),
14
  $submenuItem = jQuery( '#toplevel_page_w3tc_dashboard a[href="admin.php?page=w3tc_feature_showcase"]' ),
15
  markup = ' <span class="awaiting-mod count-' +
@@ -20,19 +21,8 @@ jQuery(function() {
20
 
21
  if ( W3TCFeatureShowcaseData.unseenCount > 0 ) {
22
  if ( $adminmenuItem.length ) {
 
23
  $adminmenuItem.append( markup );
24
-
25
- $adminmenuItem.find( '.awaiting-mod' ).css(
26
- {
27
- padding: "0 5px",
28
- "min-width": "18px",
29
- height: "18px",
30
- "border-radius": "9px",
31
- "background-color": "#ca4a1f",
32
- color: "#fff",
33
- "font-size": "11px"
34
- }
35
- );
36
  }
37
 
38
  if ( $menuItem.length ) {
9
  */
10
 
11
  jQuery(function() {
12
+ var $adminmenuTopItem = jQuery( '#wp-admin-bar-w3tc > a' ),
13
+ $adminmenuItem = jQuery ( '#wp-admin-bar-w3tc_feature_showcase a' ),
14
  $menuItem = jQuery( '#toplevel_page_w3tc_dashboard.wp-not-current-submenu a[href="admin.php?page=w3tc_dashboard"] .wp-menu-name' ),
15
  $submenuItem = jQuery( '#toplevel_page_w3tc_dashboard a[href="admin.php?page=w3tc_feature_showcase"]' ),
16
  markup = ' <span class="awaiting-mod count-' +
21
 
22
  if ( W3TCFeatureShowcaseData.unseenCount > 0 ) {
23
  if ( $adminmenuItem.length ) {
24
+ $adminmenuTopItem.append( markup );
25
  $adminmenuItem.append( markup );
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
27
 
28
  if ( $menuItem.length ) {
readme.txt CHANGED
@@ -1,9 +1,9 @@
1
  === Plugin Name ===
2
  Contributors: boldgrid, fredericktownes, maxicusc, gidomanders, bwmarkle, harryjackson1221, joemoto, vmarko, jacobd91
3
- Tags: seo, cache, optimize, pagespeed, performance, caching, compression, maxcdn, nginx, varnish, redis, new relic, aws, amazon web services, s3, cloudfront, rackspace, cloudflare, azure, apache
4
  Requires at least: 3.8
5
  Tested up to: 5.8
6
- Stable tag: 2.1.9
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -11,9 +11,9 @@ Search Engine (SEO) &amp; Performance Optimization (WPO) via caching. Integrated
11
 
12
  == Description ==
13
 
14
- W3 Total Cache (W3TC) improves the SEO and user experience of your site by increasing website performance and reducing load times by leveraging features like content delivery network (CDN) integration and the latest best practices.
15
 
16
- W3TC is the **only** web host agnostic Web Performance Optimization (WPO) framework for WordPress trusted by millions of publishers, web developers, and web hosts worldwide for more than a decade. It is the total performance solution for optimizing your WordPress Website.
17
 
18
  An inside look:
19
 
@@ -36,7 +36,7 @@ https://youtu.be/7AsNSSrZq4Y
36
  * Transparent content delivery network (CDN) management with Media Library, theme files and WordPress itself
37
  * Mobile support: respective caching of pages by referrer or groups of user agents including theme switching for groups of referrers or user agents
38
  * Accelerated Mobile Pages (AMP) support
39
- * Secure Socket Layer (SSL) support
40
  * Caching of (minified and compressed) pages and posts in memory or on disk or on (FSD) CDN (by user agent group)
41
  * Caching of (minified and compressed) CSS and JavaScript in memory, on disk or on CDN
42
  * Caching of feeds (site, categories, tags, comments, search results) in memory or on disk or on CDN
@@ -61,8 +61,9 @@ https://youtu.be/7AsNSSrZq4Y
61
  * Caching statistics for performance insights of any enabled feature
62
  * Extension framework for customization or extensibility for Cloudflare, WPML and much more
63
  * Reverse proxy integration via Nginx or Varnish
 
64
 
65
- Speed up your site tremendously, and improve the user experience for your readers without having to change WordPress, your theme, your plugins or how you produce your content.
66
 
67
  == Frequently Asked Questions ==
68
 
@@ -76,7 +77,7 @@ Speed is among the most significant success factors web sites face. In fact, you
76
  * Yahoo.com: **+400 ms** (speed decrease) -> **-5-9% full-page traffic loss** (visitor left before the page finished loading) [[2](http://www.slideshare.net/stoyan/yslow-20-presentation)]
77
  * Amazon.com: **+100 ms** (speed decrease) -> **-1% sales loss** [[1](http://home.blarg.net/~glinden/StanfordDataMining.2006-11-29.ppt)]
78
 
79
- A thousandth of a second is not a long time, yet the impact is quite significant. Even if you're not a large company (or just hope to become one), a loss is still a loss. However, there is a solution to this problem, take advantage.
80
 
81
  Many of the other consequences of poor performance were discovered more than a decade ago:
82
 
@@ -94,7 +95,7 @@ There are a number of [resources](http://www.websiteoptimization.com/speed/tweak
94
 
95
  = Why is W3 Total Cache better than other caching solutions? =
96
 
97
- **It's a complete framework.** Most cache plugins available do a great job at achieving a couple of performance aims. Our plugin remedies numerous performance reducing aspects of any web site going far beyond merely reducing CPU usage (load) and bandwidth consumption for HTML pages alone. Equally important, the plugin requires no theme modifications, modifications to your .htaccess (mod_rewrite rules) or programming compromises to get started. Most importantly, it's the only plugin designed to optimize all practical hosting environments small or large. The options are many and setup is easy.
98
 
99
  = I've never heard of any of this stuff; my site is fine, no one complains about the speed. Why should I install this? =
100
 
@@ -156,7 +157,7 @@ Use the "Help" button available on the Minify settings tab. Once open, the tool
156
 
157
  = I don't understand what a CDN has to do with caching, that's completely different, no? =
158
 
159
- Technically no, a CDN is a high performance cache that stores static assets (your theme files, media library etc) in various locations throughout the world in order to provide low latency access to them by readers in those regions.
160
 
161
  = How do I use an Origin Pull (Mirror) CDN? =
162
  Login to your CDN providers control panel or account management area. Following any set up steps they provide, create a new "pull zone" or "bucket" for your site's domain name. If there's a set up wizard or any troubleshooting tips your provider offers, be sure to review them. In the CDN tab of the plugin, enter the hostname your CDN provider provided in the "replace site's hostname with" field. You should always do a quick check by opening a test file from the CDN hostname, e.g. http://cdn.domain.com/favicon.ico. Troubleshoot with your CDN provider until this test is successful.
@@ -215,20 +216,29 @@ First, make sure the plugin is not active (disabled) network-wide. Then make sur
215
 
216
  = A notification about file owner appears along with an FTP form, how can I resolve this? =
217
 
218
- The plugin uses WordPress FileSystem functionality to write to files. It checks if the file owner, file owner group of created files match process owner. If this is not the case it cannot write or modify files.
219
 
220
  Typically, you should tell your web host about the permission issue and they should be able to resolve it.
221
 
222
  You can however try adding <em>define('FS_METHOD', 'direct');</em> to wp-config.php to circumvent the file and folder checks.
223
 
 
 
 
 
 
 
 
 
224
  = This is too good to be true, how can I test the results? =
225
 
226
- You will be able to see it instantly on each page load, but for tangible metrics, consider the following tools:
227
 
228
  * [Google Page Speed](https://developers.google.com/speed/pagespeed/)
 
229
  * [WebPagetest](https://www.webpagetest.org/test)
230
  * [Pingdom](https://tools.pingdom.com/)
231
- * [DynaTrace (formerly Gomez) Performance Test](https://www.dynatrace.com/en_us/application-performance-management/products/performance-center.html)
232
 
233
  = I don't have time to deal with this, but I know I need it. Will you help me? =
234
 
@@ -275,6 +285,9 @@ Please reach out to all of these people and support their projects if you're so
275
 
276
  == Changelog ==
277
 
 
 
 
278
  = 2.1.9 =
279
  * Fix: Cloudflare Dashboard Widget: Updated to use GraphQL
280
  * Fix: Cloudflare Dashboard Widget: Use WordPress timezone
1
  === Plugin Name ===
2
  Contributors: boldgrid, fredericktownes, maxicusc, gidomanders, bwmarkle, harryjackson1221, joemoto, vmarko, jacobd91
3
+ Tags: seo, cache, CDN, pagespeed, caching, performance, compression, optimize, cloudflare, nginx, apache, varnish, redis, aws, amazon web services, s3, cloudfront, azure
4
  Requires at least: 3.8
5
  Tested up to: 5.8
6
+ Stable tag: 2.2.0
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
11
 
12
  == Description ==
13
 
14
+ W3 Total Cache (W3TC) improves the SEO, Core Web Vitals and overall user experience of your site by increasing website performance and reducing load times by leveraging features like content delivery network (CDN) integration and the latest best practices.
15
 
16
+ W3TC is the **only** web host agnostic Web Performance Optimization (WPO) framework for WordPress trusted by millions of publishers, web developers, and web hosts worldwide for more than a decade. It is the total performance solution for optimizing WordPress Websites.
17
 
18
  An inside look:
19
 
36
  * Transparent content delivery network (CDN) management with Media Library, theme files and WordPress itself
37
  * Mobile support: respective caching of pages by referrer or groups of user agents including theme switching for groups of referrers or user agents
38
  * Accelerated Mobile Pages (AMP) support
39
+ * Secure Socket Layer (SSL/TLS) support
40
  * Caching of (minified and compressed) pages and posts in memory or on disk or on (FSD) CDN (by user agent group)
41
  * Caching of (minified and compressed) CSS and JavaScript in memory, on disk or on CDN
42
  * Caching of feeds (site, categories, tags, comments, search results) in memory or on disk or on CDN
61
  * Caching statistics for performance insights of any enabled feature
62
  * Extension framework for customization or extensibility for Cloudflare, WPML and much more
63
  * Reverse proxy integration via Nginx or Varnish
64
+ * Image Service API extension provides WebP image format conversion from common image formats (on upload and on demand)
65
 
66
+ Speed up your site tremendously, improve core web vitals and the overall user experience for your visitors without having to change your WordPress host, theme, plugins or your content production workflow.
67
 
68
  == Frequently Asked Questions ==
69
 
77
  * Yahoo.com: **+400 ms** (speed decrease) -> **-5-9% full-page traffic loss** (visitor left before the page finished loading) [[2](http://www.slideshare.net/stoyan/yslow-20-presentation)]
78
  * Amazon.com: **+100 ms** (speed decrease) -> **-1% sales loss** [[1](http://home.blarg.net/~glinden/StanfordDataMining.2006-11-29.ppt)]
79
 
80
+ A thousandth of a second is not a long time, yet the impact is quite significant. Even if you're not a large company (or just hope to become one), a loss is still a loss. W3 Total Cache is your solution for faster websites, happier visitors and better results.
81
 
82
  Many of the other consequences of poor performance were discovered more than a decade ago:
83
 
95
 
96
  = Why is W3 Total Cache better than other caching solutions? =
97
 
98
+ **It's a complete framework.** Most cache plugins available do a great job at achieving a couple of performance gains. Total Cache is different because it remedies numerous performance reducing aspects of any web site. It goes farther than the basics, beyond merely reducing CPU usage (load) or bandwidth consumption for HTML pages. Equally important, the plugin requires no theme modifications, modifications to your .htaccess (mod_rewrite rules) or programming compromises to get started. Most importantly, it's the only plugin designed to optimize all practical hosting environments small or large. The options are many and setup is easy.
99
 
100
  = I've never heard of any of this stuff; my site is fine, no one complains about the speed. Why should I install this? =
101
 
157
 
158
  = I don't understand what a CDN has to do with caching, that's completely different, no? =
159
 
160
+ Technically no, a CDN is a high performance cache that stores static assets (your theme files, media library etc) in various locations throughout the world in order to provide low latency access to them by readers in those regions. Use Total Cache to accelerate your site by putting your content closer to your users with our many CDN integrations including Cloudflare, StackPath, AWS and more.
161
 
162
  = How do I use an Origin Pull (Mirror) CDN? =
163
  Login to your CDN providers control panel or account management area. Following any set up steps they provide, create a new "pull zone" or "bucket" for your site's domain name. If there's a set up wizard or any troubleshooting tips your provider offers, be sure to review them. In the CDN tab of the plugin, enter the hostname your CDN provider provided in the "replace site's hostname with" field. You should always do a quick check by opening a test file from the CDN hostname, e.g. http://cdn.domain.com/favicon.ico. Troubleshoot with your CDN provider until this test is successful.
216
 
217
  = A notification about file owner appears along with an FTP form, how can I resolve this? =
218
 
219
+ The plugin uses WordPress FileSystem functionality to write to files. It checks if the file owner, file owner group of created files match process owner. If this is not the case it cannot write or modify files.
220
 
221
  Typically, you should tell your web host about the permission issue and they should be able to resolve it.
222
 
223
  You can however try adding <em>define('FS_METHOD', 'direct');</em> to wp-config.php to circumvent the file and folder checks.
224
 
225
+ = Does the Image Service extension use a lot of resources to convert images to WebP? =
226
+
227
+ No. The Image Service extension converts common image file formats to the modern WebP format using our API services. The conversions occur on our API service, so that resource usage does not impact your website server.
228
+
229
+ = Is image data retained by the Total Cache Image Service API? =
230
+
231
+ Image data received by our API is destroyed after a converted image is generated. The converted iamges are destroyed once picked-up/downloaded to your website by the Total Cache plugin.
232
+
233
  = This is too good to be true, how can I test the results? =
234
 
235
+ You will be able to see the results instantly on each page load, but for tangible metrics, you should consider using the following tools:
236
 
237
  * [Google Page Speed](https://developers.google.com/speed/pagespeed/)
238
+ * [Google Search Console Core Web Vitals Report](https://search.google.com/search-console/core-web-vitals/)
239
  * [WebPagetest](https://www.webpagetest.org/test)
240
  * [Pingdom](https://tools.pingdom.com/)
241
+ * [GTmetrix](https://gtmetrix.com/)
242
 
243
  = I don't have time to deal with this, but I know I need it. Will you help me? =
244
 
285
 
286
  == Changelog ==
287
 
288
+ = 2.2.0 =
289
+ * Feature: Image Service API extension: WebP conversion options
290
+
291
  = 2.1.9 =
292
  * Fix: Cloudflare Dashboard Widget: Updated to use GraphQL
293
  * Fix: Cloudflare Dashboard Widget: Use WordPress timezone
w3-total-cache-api.php CHANGED
@@ -5,7 +5,7 @@ if ( !defined( 'ABSPATH' ) ) {
5
  }
6
 
7
  define( 'W3TC', true );
8
- define( 'W3TC_VERSION', '2.1.9' );
9
  define( 'W3TC_POWERED_BY', 'W3 Total Cache' );
10
  define( 'W3TC_EMAIL', 'w3tc@w3-edge.com' );
11
  define( 'W3TC_TEXT_DOMAIN', 'w3-total-cache' );
@@ -104,7 +104,7 @@ define( 'W3TC_MARKER_BEGIN_MINIFY_CORE', '# BEGIN W3TC Minify core' );
104
  define( 'W3TC_MARKER_BEGIN_MINIFY_CACHE', '# BEGIN W3TC Minify cache' );
105
  define( 'W3TC_MARKER_BEGIN_MINIFY_LEGACY', '# BEGIN W3TC Minify' );
106
  define( 'W3TC_MARKER_BEGIN_CDN', '# BEGIN W3TC CDN' );
107
-
108
 
109
  define( 'W3TC_MARKER_END_WORDPRESS', '# END WordPress' );
110
  define( 'W3TC_MARKER_END_PGCACHE_CORE', '# END W3TC Page Cache core' );
@@ -118,7 +118,7 @@ define( 'W3TC_MARKER_END_MINIFY_CACHE', '# END W3TC Minify cache' );
118
  define( 'W3TC_MARKER_END_MINIFY_LEGACY', '# END W3TC Minify' );
119
  define( 'W3TC_MARKER_END_CDN', '# END W3TC CDN' );
120
  define( 'W3TC_MARKER_END_NEW_RELIC_CORE', '# END W3TC New Relic core' );
121
-
122
 
123
  if ( !defined( 'W3TC_EXTENSION_DIR' ) ) {
124
  define( 'W3TC_EXTENSION_DIR', ( defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/plugins' ) );
@@ -131,7 +131,6 @@ if ( !defined( 'W3TC_FEED_REGEXP' ) ) {
131
  define( 'W3TC_FEED_REGEXP', '~/feed(/|$)~' );
132
  }
133
 
134
-
135
  @ini_set( 'pcre.backtrack_limit', 4194304 );
136
  @ini_set( 'pcre.recursion_limit', 4194304 );
137
 
@@ -567,8 +566,8 @@ class W3_ConfigWriter {
567
  }
568
 
569
  /**
570
- Deprecated. Retained for 3rd parties that use it. see w3tc_config()
571
- */
572
  function w3_instance( $class ) {
573
  $legacy_class_name = null;
574
 
5
  }
6
 
7
  define( 'W3TC', true );
8
+ define( 'W3TC_VERSION', '2.2.0' );
9
  define( 'W3TC_POWERED_BY', 'W3 Total Cache' );
10
  define( 'W3TC_EMAIL', 'w3tc@w3-edge.com' );
11
  define( 'W3TC_TEXT_DOMAIN', 'w3-total-cache' );
104
  define( 'W3TC_MARKER_BEGIN_MINIFY_CACHE', '# BEGIN W3TC Minify cache' );
105
  define( 'W3TC_MARKER_BEGIN_MINIFY_LEGACY', '# BEGIN W3TC Minify' );
106
  define( 'W3TC_MARKER_BEGIN_CDN', '# BEGIN W3TC CDN' );
107
+ define( 'W3TC_MARKER_BEGIN_WEBP', '# BEGIN W3TC WEBP' );
108
 
109
  define( 'W3TC_MARKER_END_WORDPRESS', '# END WordPress' );
110
  define( 'W3TC_MARKER_END_PGCACHE_CORE', '# END W3TC Page Cache core' );
118
  define( 'W3TC_MARKER_END_MINIFY_LEGACY', '# END W3TC Minify' );
119
  define( 'W3TC_MARKER_END_CDN', '# END W3TC CDN' );
120
  define( 'W3TC_MARKER_END_NEW_RELIC_CORE', '# END W3TC New Relic core' );
121
+ define( 'W3TC_MARKER_END_WEBP', '# END W3TC WEBP' );
122
 
123
  if ( !defined( 'W3TC_EXTENSION_DIR' ) ) {
124
  define( 'W3TC_EXTENSION_DIR', ( defined( 'WP_PLUGIN_DIR' ) ? WP_PLUGIN_DIR : WP_CONTENT_DIR . '/plugins' ) );
131
  define( 'W3TC_FEED_REGEXP', '~/feed(/|$)~' );
132
  }
133
 
 
134
  @ini_set( 'pcre.backtrack_limit', 4194304 );
135
  @ini_set( 'pcre.recursion_limit', 4194304 );
136
 
566
  }
567
 
568
  /**
569
+ * Deprecated. Retained for 3rd parties that use it. see w3tc_config().
570
+ */
571
  function w3_instance( $class ) {
572
  $legacy_class_name = null;
573
 
w3-total-cache.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: W3 Total Cache
4
  * Plugin URI: https://www.boldgrid.com/totalcache/
5
  * Description: The highest rated and most complete WordPress performance plugin. Dramatically improve the speed and user experience of your site. Add browser, page, object and database caching as well as minify and content delivery network (CDN) to WordPress.
6
- * Version: 2.1.9
7
  * Requires at least: 3.8
8
  * Requires PHP: 5.6
9
  * Author: BoldGrid
3
  * Plugin Name: W3 Total Cache
4
  * Plugin URI: https://www.boldgrid.com/totalcache/
5
  * Description: The highest rated and most complete WordPress performance plugin. Dramatically improve the speed and user experience of your site. Add browser, page, object and database caching as well as minify and content delivery network (CDN) to WordPress.
6
+ * Version: 2.2.0
7
  * Requires at least: 3.8
8
  * Requires PHP: 5.6
9
  * Author: BoldGrid