| @@ -128,8 +128,9 @@ | ||
| 128 | 128 | * Regenerate config file on cache flush. |
| 129 | 129 | */ |
| 130 | 130 | add_action('wpo_cache_flush', array($this, 'update_cache_config')); |
| 131 | 131 | add_action('wpo_cache_flush', array($this, 'delete_cache_size_information')); |
| 132 | + add_action('wpo_cache_flush', array($this, 'update_cache_cleared_time')); | |
| 132 | 133 | add_action('update_option_permalink_structure', array($this, 'update_option_permalink_structure'), 10, 3); |
| 133 | 134 | |
| 134 | 135 | // Add purge cache link to admin bar. |
| 135 | 136 | add_filter('wpo_cache_admin_bar_menu_items', array($this, 'admin_bar_purge_cache'), 20, 1); |
| @@ -193,44 +194,52 @@ | ||
| 193 | 194 | * |
| 194 | 195 | * This method checks cache rules to identify if caching is possible. |
| 195 | 196 | * If caching is not allowed, it adds appropriate HTTP headers and debug messages. |
| 196 | 197 | * |
| 197 | - * @return bool True if the page should be cached, false otherwise. | |
| 198 | + * @return bool|array True if the page should be cached, false otherwise or not empty array with the reasons why the page isn't cached | |
| 198 | 199 | */ |
| 199 | 200 | public function should_cache_page() { |
| 200 | 201 | |
| 201 | 202 | if (!$this->is_enabled()) return false; |
| 202 | 203 | |
| 203 | - $no_cache_because = array(); | |
| 204 | + return wpo_can_serve_from_cache(); | |
| 205 | + } | |
| 204 | 206 | |
| 205 | - // Check serve cache rules, to identify if we need to cache the page. | |
| 206 | - $can_serve_from_cache = wpo_can_serve_from_cache(); | |
| 207 | - | |
| 208 | - if (false === $can_serve_from_cache) return false; | |
| 207 | + /** | |
| 208 | + * Maybe add information about why the page is not cached to the headers and footer output | |
| 209 | + * | |
| 210 | + * @param array $no_cache_because | |
| 211 | + * @return void | |
| 212 | + */ | |
| 213 | + public function maybe_add_no_cache_because_info($no_cache_because) { | |
| 214 | + // Add http header | |
| 215 | + if (!wp_doing_cron() && !empty($no_cache_because)) { | |
| 216 | + $no_cache_because_message = join(", ", $no_cache_because); | |
| 217 | + wpo_cache_add_nocache_http_header_with_send_headers_action($no_cache_because_message); | |
| 209 | 218 | |
| 210 | - if (is_array($can_serve_from_cache)) $no_cache_because = $can_serve_from_cache; | |
| 219 | + $not_cached_details = ""; | |
| 220 | + | |
| 221 | + // Output the reason only when the user has turned on debugging | |
| 222 | + if ((defined('WP_DEBUG') && WP_DEBUG) || !empty(TeamUpdraft\WP_Optimize\Includes\Fragments\fetch_superglobal('get', 'wpo_cache_debug'))) { | |
| 223 | + $not_cached_details = "because: ".$no_cache_because_message; | |
| 224 | + } | |
| 225 | + wpo_cache_add_footer_output(sprintf("Page not served from cache %s", $not_cached_details)); | |
| 226 | + } | |
| 227 | + } | |
| 211 | 228 | |
| 212 | - if (!empty($no_cache_because)) { | |
| 213 | - do_action('wpo_page_not_cached', $no_cache_because); | |
| 229 | + /** | |
| 230 | + * Handle cache settings update | |
| 231 | + * | |
| 232 | + * @param array $new_settings | |
| 233 | + * @return void | |
| 234 | + */ | |
| 235 | + public function cache_settings_updated($new_settings) { | |
| 214 | 236 | |
| 215 | - // Add http header | |
| 216 | - if (!wp_doing_cron()) { | |
| 217 | - $no_cache_because_message = join(", ", $no_cache_because); | |
| 218 | - wpo_cache_add_nocache_http_header_with_send_headers_action($no_cache_because_message); | |
| 237 | + $this->unschedule_purge_old_cache(); | |
| 219 | 238 | |
| 220 | - $not_cached_details = ""; | |
| 221 | - | |
| 222 | - // Output the reason only when the user has turned on debugging | |
| 223 | - if ((defined('WP_DEBUG') && WP_DEBUG) || !empty(TeamUpdraft\WP_Optimize\Includes\Fragments\fetch_superglobal('get', 'wpo_cache_debug'))) { | |
| 224 | - $not_cached_details = "because: ".$no_cache_because_message; | |
| 225 | - } | |
| 226 | - wpo_cache_add_footer_output(sprintf("Page not served from cache %s", $not_cached_details)); | |
| 227 | - } | |
| 228 | - | |
| 229 | - return false; | |
| 239 | + if ($new_settings['enable_page_caching']) { | |
| 240 | + $this->schedule_purge_old_cache(); | |
| 230 | 241 | } |
| 231 | - | |
| 232 | - return true; | |
| 233 | 242 | } |
| 234 | 243 | |
| 235 | 244 | /** |
| 236 | 245 | * Activate cron job when the cache is enabled for deleting expired cache files |
| @@ -235,23 +244,42 @@ | ||
| 235 | 244 | /** |
| 236 | 245 | * Activate cron job when the cache is enabled for deleting expired cache files |
| 237 | 246 | */ |
| 238 | 247 | public function cron_activate() { |
| 239 | - $page_cache_length = $this->config->get_option('page_cache_length'); | |
| 240 | 248 | if ($this->is_enabled()) { |
| 241 | - if (!wp_next_scheduled('wpo_purge_old_cache')) { | |
| 242 | - wp_schedule_event(time() + (false === $page_cache_length ? '86400' : $page_cache_length), 'wpo_purge_old_cache', 'wpo_purge_old_cache'); | |
| 243 | - } | |
| 244 | - if (!wp_next_scheduled('wpo_prune_cache_logs')) { | |
| 245 | - wp_schedule_event(time(), 'weekly', 'wpo_prune_cache_logs'); | |
| 246 | - } | |
| 249 | + $this->schedule_purge_old_cache(); | |
| 247 | 250 | } else { |
| 248 | - wp_clear_scheduled_hook('wpo_purge_old_cache'); | |
| 249 | - wp_clear_scheduled_hook('wpo_prune_cache_logs'); | |
| 251 | + $this->unschedule_purge_old_cache(); | |
| 250 | 252 | } |
| 251 | 253 | } |
| 252 | 254 | |
| 253 | 255 | /** |
| 256 | + * Schedules events to purge old cache | |
| 257 | + * | |
| 258 | + * @return void | |
| 259 | + */ | |
| 260 | + private function schedule_purge_old_cache() { | |
| 261 | + $page_cache_length = $this->config->get_option('page_cache_length'); | |
| 262 | + | |
| 263 | + if (!wp_next_scheduled('wpo_purge_old_cache')) { | |
| 264 | + wp_schedule_event(time() + (false === $page_cache_length ? 86400 : $page_cache_length), 'wpo_purge_old_cache', 'wpo_purge_old_cache'); | |
| 265 | + } | |
| 266 | + if (!wp_next_scheduled('wpo_prune_cache_logs')) { | |
| 267 | + wp_schedule_event(time(), 'weekly', 'wpo_prune_cache_logs'); | |
| 268 | + } | |
| 269 | + } | |
| 270 | + | |
| 271 | + /** | |
| 272 | + * Unschedules events to purge old cache | |
| 273 | + * | |
| 274 | + * @return void | |
| 275 | + */ | |
| 276 | + private function unschedule_purge_old_cache() { | |
| 277 | + wp_clear_scheduled_hook('wpo_purge_old_cache'); | |
| 278 | + wp_clear_scheduled_hook('wpo_prune_cache_logs'); | |
| 279 | + } | |
| 280 | + | |
| 281 | + /** | |
| 254 | 282 | * Do required actions on activate/deactivate any plugin. |
| 255 | 283 | */ |
| 256 | 284 | public function activate_deactivate_plugin() { |
| 257 | 285 | |
| @@ -270,15 +298,10 @@ | ||
| 270 | 298 | * @return bool |
| 271 | 299 | */ |
| 272 | 300 | public function can_purge_cache() { |
| 273 | 301 | if (!$this->is_enabled()) return false; |
| 274 | - $required_capability = is_multisite() ? 'manage_network_options' : 'manage_options'; | |
| 275 | 302 | |
| 276 | - if (WP_Optimize::is_premium()) { | |
| 277 | - return current_user_can($required_capability) || WP_Optimize_Premium()->can_purge_the_cache(); | |
| 278 | - } else { | |
| 279 | - return current_user_can($required_capability); | |
| 280 | - } | |
| 303 | + return WP_Optimize_Utils::current_user_can_purge_cache(); | |
| 281 | 304 | } |
| 282 | 305 | |
| 283 | 306 | /** |
| 284 | 307 | * Add Purge from cache in admin bar. |
| @@ -289,9 +312,9 @@ | ||
| 289 | 312 | public function admin_bar_purge_cache($menu_items) { |
| 290 | 313 | global $pagenow; |
| 291 | 314 | if (!$this->can_purge_cache()) return $menu_items; |
| 292 | 315 | |
| 293 | - $act_url = remove_query_arg(array('wpo_single_page_cache_purged', 'wpo_all_pages_cache_purged')); | |
| 316 | + $act_url = WP_Optimize_Utils::get_url_without_cache_purge_params(); | |
| 294 | 317 | |
| 295 | 318 | $cache_size = $this->get_cache_size(); |
| 296 | 319 | $cache_size_info = '<h4>'.__('Page cache', 'wp-optimize').'</h4>'; |
| 297 | 320 | // translators: %d is the number of files in the cache |
| @@ -342,16 +365,29 @@ | ||
| 342 | 365 | if (isset($_GET['wpo_single_page_cache_purged']) || isset($_GET['wpo_all_pages_cache_purged'])) { |
| 343 | 366 | // phpcs:disable |
| 344 | 367 | // We are not using $_GET values, just checks if the variable is set and then use hard coded string |
| 345 | 368 | if (isset($_GET['wpo_single_page_cache_purged'])) { |
| 346 | - $notice_function = $_GET['wpo_single_page_cache_purged'] ? 'notice_purge_single_page_cache_success' : 'notice_purge_single_page_cache_error'; | |
| 369 | + $message = $_GET['wpo_single_page_cache_purged'] ? __('Cache purged for this page', 'wp-optimize') : __('Error purging cache for this page', 'wp-optimize'); | |
| 370 | + $type = $_GET['wpo_single_page_cache_purged'] ? 'success' : 'error'; | |
| 347 | 371 | } else { |
| 348 | - $notice_function = $_GET['wpo_all_pages_cache_purged'] ? 'notice_purge_all_pages_cache_success' : 'notice_purge_all_pages_cache_error'; | |
| 372 | + $message = $_GET['wpo_all_pages_cache_purged'] ? __('Cache purged for all pages', 'wp-optimize') : __('Error purging cache for all pages', 'wp-optimize'); | |
| 373 | + $type = $_GET['wpo_all_pages_cache_purged'] ? 'success' : 'error'; | |
| 349 | 374 | } |
| 350 | 375 | // phpcs:enable |
| 351 | 376 | |
| 352 | - add_action('admin_notices', array($this, $notice_function)); | |
| 377 | + if (is_admin()) { | |
| 378 | + $admin_notices_hook = is_network_admin() ? 'network_admin_notices' : 'admin_notices'; | |
| 353 | 379 | |
| 380 | + add_action($admin_notices_hook, function() use ($message, $type) { | |
| 381 | + $this->show_notice($message, $type); | |
| 382 | + }); | |
| 383 | + | |
| 384 | + add_action('admin_head', array('WP_Optimize_Utils', 'script_to_remove_cache_purge_params_from_url')); | |
| 385 | + } else { | |
| 386 | + printf('<script>window.onload = function() {alert("%s");}</script>', esc_js($message)); | |
| 387 | + WP_Optimize_Utils::script_to_remove_cache_purge_params_from_url(); | |
| 388 | + } | |
| 389 | + | |
| 354 | 390 | return; |
| 355 | 391 | } |
| 356 | 392 | |
| 357 | 393 | if (!isset($_GET['_wpo_purge'])) return; |
| @@ -370,10 +406,13 @@ | ||
| 370 | 406 | $success = self::delete_cache_by_url($url); |
| 371 | 407 | if ($success) $this->file_log("Cache for URL: " . self::remove_query_params($url) . " has been purged, triggered by: " . __METHOD__); |
| 372 | 408 | } |
| 373 | 409 | |
| 374 | - // remove nonce from url and reload page. | |
| 375 | - wp_redirect(add_query_arg('wpo_single_page_cache_purged', $success, remove_query_arg('_wpo_purge'))); | |
| 410 | + // Remove all existing purge-related parameters and add the new one | |
| 411 | + $redirect_url = WP_Optimize_Utils::get_url_without_cache_purge_params(); | |
| 412 | + $redirect_url = add_query_arg('wpo_single_page_cache_purged', $success, $redirect_url); | |
| 413 | + | |
| 414 | + wp_redirect($redirect_url); | |
| 376 | 415 | exit; |
| 377 | 416 | |
| 378 | 417 | } elseif (wp_verify_nonce(sanitize_key($_GET['_wpo_purge']), 'wpo_purge_all_pages_cache')) { |
| 379 | 418 | $success = self::purge(); |
| @@ -379,10 +418,13 @@ | ||
| 379 | 418 | $success = self::purge(); |
| 380 | 419 | $this->maybe_set_preload_cron_job(); |
| 381 | 420 | if ($success) $this->file_log("Full Cache Purge triggered by: ". __METHOD__); |
| 382 | 421 | |
| 383 | - // remove nonce from url and reload page. | |
| 384 | - wp_redirect(add_query_arg('wpo_all_pages_cache_purged', $success, remove_query_arg('_wpo_purge'))); | |
| 422 | + // Remove all existing purge-related parameters and add the new one | |
| 423 | + $redirect_url = WP_Optimize_Utils::get_url_without_cache_purge_params(); | |
| 424 | + $redirect_url = add_query_arg('wpo_all_pages_cache_purged', $success, $redirect_url); | |
| 425 | + | |
| 426 | + wp_redirect($redirect_url); | |
| 385 | 427 | exit; |
| 386 | 428 | } |
| 387 | 429 | } |
| 388 | 430 | |
| @@ -402,38 +444,10 @@ | ||
| 402 | 444 | wp_schedule_single_event(time() + 30, 'wpo_page_cache_run_preload'); |
| 403 | 445 | } |
| 404 | 446 | } |
| 405 | 447 | } |
| 406 | - | |
| 448 | + | |
| 407 | 449 | /** |
| 408 | - * Show notification when page cache purged successfully. | |
| 409 | - */ | |
| 410 | - public function notice_purge_single_page_cache_success() { | |
| 411 | - $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success'); | |
| 412 | - } | |
| 413 | - | |
| 414 | - /** | |
| 415 | - * Show notification when page cache wasn't purged. | |
| 416 | - */ | |
| 417 | - public function notice_purge_single_page_cache_error() { | |
| 418 | - $this->show_notice(__('The page cache was not purged.', 'wp-optimize'), 'error'); | |
| 419 | - } | |
| 420 | - | |
| 421 | - /** | |
| 422 | - * Show notification when all pages cache purged successfully. | |
| 423 | - */ | |
| 424 | - public function notice_purge_all_pages_cache_success() { | |
| 425 | - $this->show_notice(__('The page cache was successfully purged.', 'wp-optimize'), 'success'); | |
| 426 | - } | |
| 427 | - | |
| 428 | - /** | |
| 429 | - * Show notification when all pages cache wasn't purged. | |
| 430 | - */ | |
| 431 | - public function notice_purge_all_pages_cache_error() { | |
| 432 | - $this->show_notice(__('The page cache was not purged.', 'wp-optimize'), 'error'); | |
| 433 | - } | |
| 434 | - | |
| 435 | - /** | |
| 436 | 450 | * Show notification in WordPress admin. |
| 437 | 451 | * |
| 438 | 452 | * @param string $message HTML (no further escaping is performed) |
| 439 | 453 | * @param string $type error, warning, success, or info |
| @@ -540,11 +554,12 @@ | ||
| 540 | 554 | |
| 541 | 555 | /** |
| 542 | 556 | * Disables page cache |
| 543 | 557 | * |
| 558 | + * @param bool $keep_option_enabled - Whether to keep the option `enable_page_caching` enabled or not. This is useful when we want to disable the cache temporarily, e.g. when deactivating the plugin, but we want to keep the option enabled so that it can be re-enabled when the plugin is reactivated. | |
| 544 | 559 | * @return bool|WP_Error - true on success or WP_Error on failure. |
| 545 | 560 | */ |
| 546 | - public function disable() { | |
| 561 | + public function disable($keep_option_enabled = false) { | |
| 547 | 562 | global $is_apache; |
| 548 | 563 | |
| 549 | 564 | $ret = true; |
| 550 | 565 | |
| @@ -586,11 +601,18 @@ | ||
| 586 | 601 | wp_clear_scheduled_hook('wpo_purge_old_cache'); |
| 587 | 602 | |
| 588 | 603 | if (!is_wp_error($ret)) { |
| 589 | 604 | wp_clear_scheduled_hook('wpo_prune_cache_logs'); |
| 590 | - $ret = $this->update_page_cache_enabled_state(false); | |
| 605 | + | |
| 606 | + if (!$keep_option_enabled) { | |
| 607 | + $ret = $this->update_page_cache_enabled_state(false); | |
| 608 | + } | |
| 591 | 609 | } |
| 592 | 610 | |
| 611 | + if (!is_wp_error($ret)) { | |
| 612 | + do_action('wpo_page_cache_disabled'); | |
| 613 | + } | |
| 614 | + | |
| 593 | 615 | return $ret; |
| 594 | 616 | } |
| 595 | 617 | |
| 596 | 618 | /** |
| @@ -609,8 +631,9 @@ | ||
| 609 | 631 | return true; |
| 610 | 632 | } |
| 611 | 633 | |
| 612 | 634 | $cache_config['enable_page_caching'] = $enabled; |
| 635 | + | |
| 613 | 636 | return $config->update($cache_config, true); |
| 614 | 637 | } |
| 615 | 638 | |
| 616 | 639 | /** |
| @@ -715,36 +738,40 @@ | ||
| 715 | 738 | return true; |
| 716 | 739 | } |
| 717 | 740 | |
| 718 | 741 | /** |
| 719 | - * Check if cache is enabled and working | |
| 742 | + * Checks whether page caching is turned on in settings. | |
| 720 | 743 | * |
| 721 | - * @return bool - true on success, false otherwise | |
| 744 | + * @return bool true if the `enable_page_caching` option is set & environment is ready to cache, false otherwise. | |
| 722 | 745 | */ |
| 723 | 746 | public function is_enabled() { |
| 747 | + return $this->config->get_option('enable_page_caching') && $this->is_environment_functional(); | |
| 748 | + } | |
| 724 | 749 | |
| 725 | - if (!$this->config->get_option('enable_page_caching')) return false; | |
| 750 | + /** | |
| 751 | + * Checks whether the cache delivery chain is actually intact and able to serve cached pages. | |
| 752 | + * | |
| 753 | + * Verifies the WP_CACHE and WPO_ADVANCED_CACHE constants are defined and true, and that the cache config file exists on disk. | |
| 754 | + * | |
| 755 | + * @return bool True if the delivery chain is functional, false otherwise. | |
| 756 | + */ | |
| 757 | + private function is_environment_functional() { | |
| 758 | + $is_cli_or_ajax = (defined('WP_CLI') && WP_CLI) || (defined('DOING_AJAX') && DOING_AJAX); | |
| 726 | 759 | |
| 727 | - if (!defined('WP_CACHE') || !WP_CACHE) { | |
| 728 | - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) { | |
| 729 | - $this->log("WP_CACHE constant is not present in wp-config.php"); | |
| 730 | - return false; | |
| 731 | - } | |
| 760 | + if (!$is_cli_or_ajax && (!defined('WP_CACHE') || !WP_CACHE)) { | |
| 761 | + $this->file_log("WP_CACHE constant is not present in wp-config.php"); | |
| 762 | + return false; | |
| 732 | 763 | } |
| 733 | 764 | |
| 734 | - if (!defined('WPO_ADVANCED_CACHE') || !WPO_ADVANCED_CACHE) { | |
| 735 | - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) { | |
| 736 | - $this->log("WPO_ADVANCED_CACHE constant is not present in advanced-cache.php"); | |
| 737 | - return false; | |
| 738 | - } | |
| 765 | + if (!$is_cli_or_ajax && (!defined('WPO_ADVANCED_CACHE') || !WPO_ADVANCED_CACHE)) { | |
| 766 | + $this->file_log("WPO_ADVANCED_CACHE constant is not present in advanced-cache.php"); | |
| 767 | + return false; | |
| 739 | 768 | } |
| 740 | 769 | |
| 741 | 770 | $config_file = WPO_CACHE_CONFIG_DIR . '/'.$this->config->get_cache_config_filename(); |
| 742 | - if (!file_exists($config_file)) { | |
| 743 | - if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) { | |
| 744 | - $this->log("Cache config file $config_file is not present"); | |
| 745 | - return false; | |
| 746 | - } | |
| 771 | + if (!$is_cli_or_ajax && !file_exists($config_file)) { | |
| 772 | + $this->file_log("Cache config file $config_file is not present"); | |
| 773 | + return false; | |
| 747 | 774 | } |
| 748 | 775 | |
| 749 | 776 | return true; |
| 750 | 777 | } |
| @@ -1012,8 +1039,13 @@ | ||
| 1012 | 1039 | if (!$config_path) { |
| 1013 | 1040 | return false; |
| 1014 | 1041 | } |
| 1015 | 1042 | |
| 1043 | + // Bail early if wp-config.php is not writable, to avoid a PHP warning. | |
| 1044 | + if (!wp_is_writable($config_path)) { | |
| 1045 | + return false; | |
| 1046 | + } | |
| 1047 | + | |
| 1016 | 1048 | $config_file_string = file_get_contents($config_path); |
| 1017 | 1049 | |
| 1018 | 1050 | // Config file is empty. Maybe couldn't read it? |
| 1019 | 1051 | if (empty($config_file_string)) { |
| @@ -1142,8 +1174,17 @@ | ||
| 1142 | 1174 | delete_site_transient('wpo_get_cache_size'); |
| 1143 | 1175 | } |
| 1144 | 1176 | |
| 1145 | 1177 | /** |
| 1178 | + * Update cache cleared time | |
| 1179 | + */ | |
| 1180 | + public function update_cache_cleared_time(): void { | |
| 1181 | + $config = $this->config->get(); | |
| 1182 | + $config['last_cleared'] = time(); | |
| 1183 | + $this->config->update($config); | |
| 1184 | + } | |
| 1185 | + | |
| 1186 | + /** | |
| 1146 | 1187 | * Get current cache size. |
| 1147 | 1188 | * |
| 1148 | 1189 | * @return array |
| 1149 | 1190 | */ |
| @@ -1252,8 +1293,10 @@ | ||
| 1252 | 1293 | $preloader = WP_Optimize_Page_Cache_Preloader::instance(); |
| 1253 | 1294 | $preloader->add_url_to_preload_list($url); |
| 1254 | 1295 | $url_task_creator = array($preloader, 'create_tasks_for_auto_preload_urls'); |
| 1255 | 1296 | |
| 1297 | + $preloader->maybe_dont_close_browser_connection(); | |
| 1298 | + | |
| 1256 | 1299 | if (!has_action('shutdown', $url_task_creator)) { |
| 1257 | 1300 | add_action('shutdown', $url_task_creator); |
| 1258 | 1301 | } |
| 1259 | 1302 | } |
| @@ -1402,9 +1445,9 @@ | ||
| 1402 | 1445 | * @return void |
| 1403 | 1446 | */ |
| 1404 | 1447 | public function admin_init() { |
| 1405 | 1448 | // Maybe update the advanced cache. |
| 1406 | - if ((!defined('DOING_AJAX') || !DOING_AJAX) && current_user_can('update_plugins')) { | |
| 1449 | + if ((!defined('DOING_AJAX') || !DOING_AJAX) && WP_Optimize()->current_user_can('update_plugins')) { | |
| 1407 | 1450 | $this->maybe_update_advanced_cache(); |
| 1408 | 1451 | $this->cron_activate(); |
| 1409 | 1452 | } |
| 1410 | 1453 | } |