PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 3.6.3
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v3.6.3
trunk 1.0 1.0.1 1.1 1.1.1 1.1.2 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1 2.1.1 2.1.2 2.2 2.2.1 All 69 releases
← All changes | includes/classes/AdvancedCache.php +326 -53 2.13.6.3 View file →
@@ -6,13 +6,15 @@
6 6 */
7 7
8 8 namespace PoweredCache;
9 9
10 +use PoweredCache\Async\CachePurger;
10 11 use const PoweredCache\Constants\POST_META_DISABLE_CACHE_KEY;
11 12 use function PoweredCache\Utils\clean_page_cache_dir;
12 13 use function PoweredCache\Utils\clean_site_cache_dir;
13 14 use function PoweredCache\Utils\delete_page_cache;
14 15 use function PoweredCache\Utils\get_post_related_urls;
16 +use const PoweredCache\Constants\PURGE_CACHE_PLUGIN_NOTICE_TRANSIENT;
15 17
16 18 if ( ! defined( 'ABSPATH' ) ) {
17 19 exit; // Exit if accessed directly.
18 20 }
@@ -24,8 +26,23 @@
24 26 */
25 27 class AdvancedCache {
26 28
27 29 /**
30 + * Holds plugin settings
31 + *
32 + * @var array $settings
33 + */
34 + private $settings;
35 +
36 +
37 + /**
38 + * Instance of CachePreloader
39 + *
40 + * @var CachePurger
41 + */
42 + private $cache_purger;
43 +
44 + /**
28 45 * Return an instance of the current class
29 46 *
30 47 * @return AdvancedCache
31 48 * @since 1.0
@@ -47,26 +64,31 @@
47 64 *
48 65 * @since 1.0
49 66 */
50 67 public function setup() {
51 - $settings = \PoweredCache\Utils\get_settings();
68 + $this->settings = \PoweredCache\Utils\get_settings();
52 69
53 - if ( ! $settings['enable_page_cache'] ) {
70 + if ( ! $this->settings['enable_page_cache'] ) {
54 71 return;
55 72 }
56 73
74 + $this->cache_purger = CachePurger::factory();
75 +
57 76 add_action( 'admin_bar_menu', array( $this, 'admin_bar_menu' ) );
58 77 add_action( 'admin_post_powered_cache_purge_page_cache', array( $this, 'purge_page_cache' ) );
59 78 add_action( 'admin_post_powered_cache_purge_page_cache_network', array( $this, 'purge_page_cache_network_wide' ) );
60 - add_action( 'pre_post_update', array( $this, 'purge_on_post_update' ), 10, 1 );
61 - add_action( 'save_post', array( $this, 'purge_on_post_update' ), 10, 1 );
62 - add_action( 'wp_trash_post', array( $this, 'purge_on_post_update' ), 10, 1 );
79 + add_action( 'transition_post_status', array( $this, 'purge_on_post_update' ), 9999, 3 );
63 80 add_action( 'switch_theme', array( $this, 'purge_on_switch_theme' ) );
64 - add_action( 'wp_set_comment_status', array( $this, 'purge_post_on_comment_status_change' ), 10, 2 );
81 + add_action( 'wp_set_comment_status', array( $this, 'purge_post_on_comment_update' ) );
82 + add_action( 'edit_comment', array( $this, 'purge_post_on_comment_update' ) );
65 83 add_action( 'set_comment_cookies', array( $this, 'set_comment_cookie' ), 10, 2 );
66 84 add_filter( 'powered_cache_post_related_urls', array( $this, 'powered_cache_post_related_urls' ) );
67 85 add_filter( 'powered_cache_page_cache_enable', array( $this, 'maybe_caching_disabled' ) );
68 86 add_filter( 'powered_cache_mod_rewrite', array( $this, 'maybe_disable_mod_rewrite' ), 99 );
87 + add_action( 'wp_update_site', array( $this, 'purge_on_site_update' ), 10, 2 );
88 + add_action( 'create_term', array( $this, 'purge_on_term_change' ), 10, 3 );
89 + add_action( 'edit_term', array( $this, 'purge_on_term_change' ), 10, 3 );
90 + add_action( 'delete_term', array( $this, 'purge_on_term_change' ), 10, 3 );
69 91 }
70 92
71 93 /**
72 94 * Add purge button on admin bar
@@ -95,8 +117,19 @@
95 117 'href' => wp_nonce_url( admin_url( 'admin-post.php?action=powered_cache_purge_page_cache' ), 'powered_cache_purge_page_cache' ),
96 118 'parent' => 'powered-cache',
97 119 )
98 120 );
121 +
122 + if ( is_singular() && ! is_preview() && is_post_publicly_viewable( get_the_ID() ) ) {
123 + $wp_admin_bar->add_menu(
124 + array(
125 + 'id' => 'advanced-cache-current-page-purge',
126 + 'title' => esc_html__( 'Purge Current Page', 'powered-cache' ),
127 + 'href' => wp_nonce_url( admin_url( sprintf( 'admin-post.php?action=powered_cache_purge_page_cache&type=current-page&post=%d', get_the_ID() ) ), 'powered_cache_purge_page_cache' ),
128 + 'parent' => 'powered-cache',
129 + )
130 + );
131 + }
99 132 }
100 133 }
101 134
102 135
@@ -105,19 +138,26 @@
105 138 *
106 139 * @since 2.0
107 140 */
108 141 public function purge_page_cache_network_wide() {
109 - if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'powered_cache_purge_page_cache_network' ) ) {
142 + if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'powered_cache_purge_page_cache_network' ) ) { // phpcs:ignore
110 143 wp_nonce_ays( '' );
111 144 }
112 145
113 146 if ( current_user_can( 'manage_network' ) ) {
114 - clean_page_cache_dir();
147 + if ( $this->settings['async_cache_cleaning'] ) {
148 + $this->cache_purger->push_to_queue( [ 'call' => 'clean_page_cache_dir' ] );
149 + $this->cache_purger->save()->dispatch();
150 + } else {
151 + clean_page_cache_dir();
152 + }
115 153 $redirect_url = add_query_arg( 'pc_action', 'flush_page_cache_network', wp_get_referer() );
116 154 } else {
117 155 $redirect_url = add_query_arg( 'pc_action', 'flush_page_cache_network_err_permission', wp_get_referer() );
118 156 }
119 157
158 + delete_site_transient( PURGE_CACHE_PLUGIN_NOTICE_TRANSIENT );
159 +
120 160 wp_safe_redirect( esc_url_raw( $redirect_url ) );
121 161 exit;
122 162 }
123 163
@@ -127,19 +167,29 @@
127 167 * @since 1.0
128 168 * @since 1.1 clean site dir instead of root page caching dir
129 169 */
130 170 public function purge_page_cache() {
131 - if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'powered_cache_purge_page_cache' ) ) {
171 + if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'powered_cache_purge_page_cache' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
132 172 wp_nonce_ays( '' );
133 173 }
134 174
135 175 if ( current_user_can( 'manage_options' ) ) {
136 - clean_site_cache_dir();
176 + if ( isset( $_GET['type'] ) && 'current-page' === $_GET['type'] && ! empty( $_GET['post'] ) ) {
177 + $this->purge_post( absint( $_GET['post'] ) );
178 + } elseif ( $this->settings['async_cache_cleaning'] ) {
179 + $this->cache_purger->push_to_queue( [ 'call' => 'clean_site_cache_dir' ] );
180 + $this->cache_purger->save()->dispatch();
181 + } else {
182 + clean_site_cache_dir();
183 + }
184 +
137 185 $redirect_url = add_query_arg( 'pc_action', 'flush_page_cache', wp_get_referer() );
138 186 } else {
139 187 $redirect_url = add_query_arg( 'pc_action', 'flush_page_cache_err_permission', wp_get_referer() );
140 188 }
141 189
190 + delete_transient( PURGE_CACHE_PLUGIN_NOTICE_TRANSIENT );
191 +
142 192 wp_safe_redirect( esc_url_raw( $redirect_url ) );
143 193 exit;
144 194 }
145 195
@@ -144,37 +194,80 @@
144 194 }
145 195
146 196
147 197 /**
198 + * Purge cache when post updated
199 + *
200 + * @param string $new_status New Post status.
201 + * @param string $old_status Old Post status.
202 + * @param \WP_Post $post Post object.
203 + *
204 + * @return void
205 + * @since 3.4 adjusted for `transition_post_status` hook
206 + * @since 1.0
207 + */
208 + public function purge_on_post_update( $new_status, $old_status, $post ) {
209 + if ( 'publish' === $new_status || 'publish' === $old_status ) {
210 + $this->purge_post( $post->ID );
211 + }
212 + }
213 +
214 + /**
148 215 * Purge post related page cache
149 216 *
150 217 * @param int $post_id Post ID
151 218 *
152 - * @since 1.0
219 + * @since 3.4
153 220 */
154 - public function purge_on_post_update( $post_id ) {
155 - $current_post_status = get_post_status( $post_id );
221 + public function purge_post( $post_id ) {
222 + $current_post = get_post( $post_id );
156 223
157 - $urls = array();
224 + if ( ! is_a( $current_post, 'WP_Post' ) ) {
225 + return;
226 + }
158 227
159 - if ( in_array( $current_post_status, array( 'publish', 'trash' ), true ) ) {
160 - $urls = get_post_related_urls( $post_id );
228 + if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
229 + return;
230 + }
161 231
162 - /**
163 - * Page cache purge urls.
164 - *
165 - * @hook powered_cache_advanced_cache_purge_urls
166 - *
167 - * @param {array} $urls The list of URLs that will purged
168 - * @param {int} $post_id Post ID.
169 - *
170 - * @return {array} New value
171 - * @since 1.0
172 - */
173 - $urls = apply_filters( 'powered_cache_advanced_cache_purge_urls', $urls, $post_id );
232 + if ( ! is_post_type_viewable( $current_post->post_type ) ) {
233 + return;
234 + }
174 235
236 + if ( ! in_array( $current_post->post_status, array( 'publish', 'private', 'trash', 'pending', 'draft' ), true ) ) {
237 + return;
238 + }
239 +
240 + $urls = get_post_related_urls( $post_id );
241 +
242 + /**
243 + * Page cache purge urls.
244 + *
245 + * @hook powered_cache_advanced_cache_purge_urls
246 + *
247 + * @param {array} $urls The list of URLs that will purged
248 + * @param {int} $post_id Post ID.
249 + *
250 + * @return {array} New value
251 + * @since 1.0
252 + */
253 + $urls = apply_filters( 'powered_cache_advanced_cache_purge_urls', $urls, $post_id );
254 + $deleted_urls = [];
255 +
256 + if ( $this->settings['async_cache_cleaning'] ) {
257 + $this->cache_purger->push_to_queue(
258 + [
259 + 'call' => 'delete_page_cache',
260 + 'urls' => $urls,
261 + ]
262 + );
263 + $deleted_urls = $urls;
264 + $this->cache_purger->save()->dispatch();
265 + } else {
175 266 foreach ( $urls as $url ) {
176 - delete_page_cache( $url );
267 + if ( delete_page_cache( $url ) ) {
268 + $deleted_urls[] = $url;
269 + }
177 270 }
178 271 }
179 272
180 273 /**
@@ -182,39 +275,61 @@
182 275 *
183 276 * @hook powered_cache_advanced_cache_purge_post
184 277 *
185 278 * @param {int} $post_id The Post ID.
186 - * @param {array} $urls The list of related urls with the updated post.
279 + * @param {array} $deleted_urls The list of purged urls with the updated post.
280 + * @param {array} $urls The list of urls that will be purged. @since 3.6
187 281 *
188 282 * @since 1.0
189 283 */
190 - do_action( 'powered_cache_advanced_cache_purge_post', $post_id, $urls );
284 + do_action( 'powered_cache_advanced_cache_purge_post', $post_id, $deleted_urls, $urls );
191 285 }
192 286
193 287 /**
194 - * Delete page cache when post update
288 + * Purge post cache when comment status change
195 289 *
196 290 * @param int $comment_id Comment ID
197 - * @param string $comment_status Status of the comment
291 + * @param string $comment_status Comment status
198 292 *
199 - * @since 1.0
293 + * @return void
294 + * @deprecated since 3.4.4
200 295 */
201 296 public function purge_post_on_comment_status_change( $comment_id, $comment_status ) {
297 + // deprecate this method in favor of `purge_post_on_comment_update`
298 + _deprecated_function( __METHOD__, '3.4.4', 'purge_post_on_comment_update' );
299 + $this->purge_post_on_comment_update( $comment_id );
202 300 $comment = get_comment( $comment_id );
203 301 $post_id = $comment->comment_post_ID;
204 302 $post_url = get_permalink( $post_id );
205 - delete_page_cache( $post_url );
206 303
304 + do_action( 'powered_cache_advanced_cache_purge_on_comment_update', $post_id, $post_url, $comment_id );
305 + }
306 +
307 + /**
308 + * Delete page cache when a comment update
309 + *
310 + * @param int $comment_id Comment ID
311 + *
312 + * @since 3.4.4
313 + */
314 + public function purge_post_on_comment_update( $comment_id ) {
315 + $comment = get_comment( $comment_id );
316 + $post_id = $comment->comment_post_ID;
317 + $post_url = get_permalink( $post_id );
318 + delete_page_cache( $post_url, true );
319 +
207 320 /**
208 321 * Fires after purging cache for a post that associated a comment
209 322 *
323 + * @hook powered_cache_advanced_cache_purge_on_comment_update
324 + *
210 325 * @param {int} $post_id Post ID.
211 326 * @param {string} $post_url Post permalink.
212 327 * @param {int} $comment_id Comment ID.
213 328 *
214 - * @since 2.0
329 + * @since 3.4.4
215 330 */
216 - do_action( 'powered_cache_advanced_cache_purge_on_comment_status_change', $post_id, $post_url, $comment_id );
331 + do_action( 'powered_cache_advanced_cache_purge_on_comment_update', $post_id, $post_url, $comment_id );
217 332 }
218 333
219 334 /**
220 335 * Purge site when switching to a new theme
@@ -221,12 +336,37 @@
221 336 *
222 337 * @since 2.0
223 338 */
224 339 public function purge_on_switch_theme() {
225 - clean_site_cache_dir();
340 + if ( $this->settings['async_cache_cleaning'] ) {
341 + $this->cache_purger->push_to_queue( [ 'call' => 'clean_site_cache_dir' ] );
342 + $this->cache_purger->save()->dispatch();
343 + } else {
344 + clean_site_cache_dir();
345 + }
226 346 }
227 347
228 348 /**
349 + * Purge site cache when site updated (eg: archived, deleted etc...)
350 + *
351 + * @param \WP_Site $new_site New site object.
352 + * @param \WP_Site $old_site Old site object.
353 + *
354 + * @return void
355 + * @since 2.5.3
356 + */
357 + public function purge_on_site_update( $new_site, $old_site ) {
358 + switch_to_blog( $old_site->id );
359 + if ( $this->settings['async_cache_cleaning'] ) {
360 + $this->cache_purger->push_to_queue( [ 'call' => 'clean_site_cache_dir' ] );
361 + $this->cache_purger->save()->dispatch();
362 + } else {
363 + clean_site_cache_dir();
364 + }
365 + restore_current_blog();
366 + }
367 +
368 + /**
229 369 * Leave a cookie when commenting on a post as usual and don't show the cached page.
230 370 * `comment_author_*` cookies are site-wide available.
231 371 * Don't show cached results just for left a comment 5 months ago is not seems efficient here.
232 372 *
@@ -287,9 +427,19 @@
287 427 if ( ! empty( $settings['rejected_cookies'] ) ) {
288 428 $cookies = preg_split( '#(\r\n|\r|\n)#', $settings['rejected_cookies'], - 1, PREG_SPLIT_NO_EMPTY );
289 429 }
290 430
291 - $wp_cookies = [ 'wp-postpass', 'wordpressuser_', 'wordpresspass_', 'wordpress_sec_', 'wordpress_logged_in_', 'powered_cache_commented_posts' ];
431 + $wp_cookies = [
432 + 'wp-postpass',
433 + 'wordpressuser_',
434 + 'wordpresspass_',
435 + 'wordpress_sec_',
436 + 'wordpress_logged_in_',
437 + 'powered_cache_commented_posts',
438 + 'comment_author_',
439 + 'comment_author_email_',
440 + 'comment_author_url_',
441 + ];
292 442
293 443 if ( ! empty( $cookies ) ) {
294 444 $wp_cookies = array_merge( $cookies, $wp_cookies );
295 445 }
@@ -333,9 +483,35 @@
333 483 */
334 484 return apply_filters( 'powered_cache_vary_cookies', $cookies );
335 485 }
336 486
487 + /**
488 + * Get the list of rejected referrers
489 + *
490 + * @return mixed|null
491 + * @since 3.6
492 + */
493 + public static function get_rejected_referrers() {
494 + $settings = \PoweredCache\Utils\get_settings();
495 + $rejected_referrers = [];
337 496
497 + if ( ! empty( $settings['rejected_referrers'] ) ) {
498 + $rejected_referrers = preg_split( '#(\r\n|\r|\n)#', $settings['rejected_referrers'], - 1, PREG_SPLIT_NO_EMPTY );
499 + }
500 +
501 + /**
502 + * Filter rejected referrer list.
503 + *
504 + * @hook powered_cache_rejected_referrers
505 + *
506 + * @param {array} $rejected_referrers The referrer that will not see the cached page.
507 + *
508 + * @return {array} Rejected referrer list.
509 + * @since 3.6
510 + */
511 + return apply_filters( 'powered_cache_rejected_referrers', $rejected_referrers );
512 + }
513 +
338 514 /**
339 515 * Get the list of rejected user agents
340 516 *
341 517 * @return mixed|void
@@ -364,9 +540,9 @@
364 540 return apply_filters( 'powered_cache_rejected_user_agents', $rejected_user_agents );
365 541 }
366 542
367 543 /**
368 - * Get the list of rejected uri that nerver get cached
544 + * Get the list of rejected uri that never get cached
369 545 *
370 546 * @return mixed|void
371 547 * @since 2.0
372 548 */
@@ -382,9 +558,9 @@
382 558 * Filter rejected uri list
383 559 *
384 560 * @hook powered_cache_rejected_uri_list
385 561 *
386 - * @param {array} $rejected_uri_list The list of rejected uri that nerver get cached.
562 + * @param {array} $rejected_uri_list The list of rejected uri that never get cached.
387 563 *
388 564 * @return {array} New value
389 565 * @since 2.0
390 566 */
@@ -392,18 +568,67 @@
392 568 }
393 569
394 570
395 571 /**
572 + * Get the allowed cache query parameters
573 + *
574 + * @return mixed|null
575 + * @since 3.0
576 + */
577 + public static function get_cache_query_string() {
578 + $settings = \PoweredCache\Utils\get_settings();
579 + $cache_query_strings = [];
580 +
581 + if ( ! empty( $settings['cache_query_strings'] ) ) {
582 + $cache_query_strings = preg_split( '#(\r\n|\r|\n)#', $settings['cache_query_strings'], - 1, PREG_SPLIT_NO_EMPTY );
583 + }
584 +
585 + $query_strings = [
586 + 'lang',
587 + ];
588 +
589 + if ( ! empty( $cache_query_strings ) ) {
590 + $cache_query_strings = array_merge( $query_strings, $cache_query_strings );
591 + }
592 +
593 + /**
594 + * Filter accepted query strings.
595 + *
596 + * @hook powered_cache_cache_query_strings
597 + *
598 + * @param {array} $query_strings The list of query strings that will be cached based on their value
599 + *
600 + * @return {array} New value
601 + * @since 3.0
602 + */
603 + return apply_filters( 'powered_cache_cache_query_strings', $cache_query_strings );
604 + }
605 +
606 + /**
396 607 * These query strings will be ignored during the caching
397 608 *
609 + * @return array
610 + * @since 3.0 deprecated
611 + * @deprecated Use `self::get_ignored_query_strings` instead
612 + */
613 + public static function get_accepted_query_strings() {
614 + _deprecated_function( '\PoweredCache\AdvancedCache::get_accepted_query_strings', '3.0', '\PoweredCache\AdvancedCache::get_ignored_query_strings' );
615 +
616 + return self::get_ignored_query_strings();
617 + }
618 +
619 + /**
620 + * These query strings will be ignored during the caching
621 + *
398 622 * @return mixed|void
623 + * @since 3.0
399 624 */
400 - public static function get_accepted_query_strings() {
401 - $settings = \PoweredCache\Utils\get_settings();
402 - $accepted_query_strings = [];
625 + public static function get_ignored_query_strings() {
626 + $settings = \PoweredCache\Utils\get_settings();
627 + $ignored_query_strings = [];
403 628
404 - if ( ! empty( $settings['accepted_query_strings'] ) ) {
405 - $accepted_query_strings = preg_split( '#(\r\n|\r|\n)#', $settings['accepted_query_strings'], - 1, PREG_SPLIT_NO_EMPTY );
629 + if ( ! empty( $settings['ignored_query_strings'] ) ) {
630 + $ignored_query_strings = preg_split( '#(\r\n|\r|\n)#', $settings['ignored_query_strings'], - 1, PREG_SPLIT_NO_EMPTY );
406 631 }
407 632
408 633 $query_strings = [
409 634 'fbclid',
@@ -427,26 +652,40 @@
427 652 'age-verified',
428 653 'usqp',
429 654 'cn-reloaded',
430 655 'ao_noptimize',
431 - 'lang',
432 656 ];
433 657
434 - if ( ! empty( $accepted_query_strings ) ) {
435 - $query_strings = array_merge( $query_strings, $accepted_query_strings );
658 + if ( ! empty( $ignored_query_strings ) ) {
659 + $query_strings = array_merge( $query_strings, $ignored_query_strings );
436 660 }
437 661
438 662 /**
439 - * Filter accepted query strings.
663 + * Filters ignored query strings.
440 664 *
441 - * @hook powered_cache_accepted_query_strings
665 + * @hook powered_cache_accepted_query_strings
442 666 *
667 + * @param {array} $query_strings The list of query strings will be ignored during the caching
668 + *
669 + * @return {array} New value
670 + * @since 2.0
671 + * @depreacated since 3.0
672 + */
673 + $query_strings = apply_filters( 'powered_cache_accepted_query_strings', $query_strings );
674 +
675 + /**
676 + * Filters ignored query strings.
677 + *
678 + * @hook powered_cache_ignored_query_strings
679 + *
443 680 * @param {array} $query_strings The list of query strings will be ignored during the caching
444 681 *
445 682 * @return {array} New value
446 - * @since 2.0
683 + * @since 3.0
447 684 */
448 - return apply_filters( 'powered_cache_accepted_query_strings', $query_strings );
685 + $query_strings = apply_filters( 'powered_cache_ignored_query_strings', $query_strings );
686 +
687 + return $query_strings;
449 688 }
450 689
451 690
452 691 /**
@@ -481,8 +720,42 @@
481 720 $rewrite_status = false;
482 721 }
483 722
484 723 return $rewrite_status;
724 + }
725 +
726 +
727 + /**
728 + * Purge cache when term change
729 + *
730 + * @param int $term_id Term ID
731 + * @param int $tt_id Term Taxonomy ID
732 + * @param string $taxonomy Taxonomy
733 + *
734 + * @since 3.4.2
735 + */
736 + public function purge_on_term_change( $term_id, $tt_id, $taxonomy ) {
737 + $term_taxonomy = get_taxonomy( $taxonomy );
738 +
739 + if ( ! $term_taxonomy->public ) {
740 + return;
741 + }
742 +
743 + $term_url = get_term_link( $term_id, $taxonomy );
744 +
745 + if ( ! is_wp_error( $term_url ) ) {
746 + if ( $this->settings['async_cache_cleaning'] ) {
747 + $this->cache_purger->push_to_queue(
748 + [
749 + 'call' => 'delete_page_cache',
750 + 'urls' => $term_url,
751 + ]
752 + );
753 + $this->cache_purger->save()->dispatch();
754 + } else {
755 + delete_page_cache( $term_url );
756 + }
757 + }
485 758 }
486 759
487 760 }
488 761