get_boolean( 'config.check' ) && ! $force_all_checks ) { return; } $exs = new Util_Environment_Exceptions(); self::apply( $w3tc_config, $exs ); if ( \count( $exs->exceptions() ) > 0 ) { throw $exs; } } /** * Fixes the environment when the configuration changes. * * @since 2.11.0 * * @param Config $w3tc_config W3TC Config containing relevant settings. * @param string $event Event key. * * @throws Util_Environment_Exceptions When the rules file cannot be written. * * @return void */ public static function fix_on_event( $w3tc_config, $event ) { // The one event W3TC rewrites its own rules on; writing ours on the others would leave both rule sets, or neither. if ( 'config_change' !== $event ) { return; } $exs = new Util_Environment_Exceptions(); self::apply( $w3tc_config, $exs ); if ( \count( $exs->exceptions() ) > 0 ) { throw $exs; } } /** * Takes the block out and forgets what was stored, on deactivation of the plugin. * * @since 2.11.0 * * @throws Util_Environment_Exceptions When the block could not be taken out. * * @return void */ public static function fix_after_deactivation() { $exs = self::withdraw(); Extension_MaxCache_Core::forget(); if ( \count( $exs->exceptions() ) > 0 ) { throw $exs; } } /** * Takes the block out when the owner switches the extension off. * * Nothing is thrown: the caller turns an exception into an audit line and answers false, which * drops the confirmation for a deactivation that did persist. A block that could not be taken out * is recorded instead, since no later pass of this extension can report it. * * @since 2.11.0 * * @return void */ public static function deactivate_extension() { $exs = self::withdraw(); // The last pass this extension makes: switched off, no later pass can correct a record remove() left standing. $path = Util_Rule::get_apache_rules_path(); $left = Extension_MaxCache_Core::rules_file_contents(); // Only on a file that could be read and no longer carries the block - unreadable keeps the record as remove() left it. if ( \is_string( $left ) && false === \strpos( $left, W3TC_MARKER_BEGIN_PGCACHE_MAXCACHE ) ) { Extension_MaxCache_Core::note_delivering( false ); } elseif ( \count( $exs->exceptions() ) > 0 ) { // Nothing of this extension loads after this, so the owner's only trace is W3TC's own audit log. Util_Debug::audit_log( 'maxcache_block_left_behind', array( 'path' => (string) $path, 'reason' => $exs->exceptions()[0]->getMessage(), ) ); } // The observation stays: it describes the infrastructure, not the switch. Extension_MaxCache_Core::forget(); } /** * Takes the block out, collecting what the removal reported. * * @since 2.11.0 * * @return Util_Environment_Exceptions What the removal reported. */ private static function withdraw() { $exs = new Util_Environment_Exceptions(); self::remove( $exs ); // remove() already drops the record when the block is gone; a second delete here is one round trip for nothing. return $exs; } /** * Writes the block, or takes it out when this configuration cannot be handed over. * * @since 2.11.0 * * @param Config $w3tc_config W3TC Config containing relevant settings. * @param object $exs Exception handler object. * * @return void */ private static function apply( $w3tc_config, $exs ) { $was_delivering = Extension_MaxCache_Core::is_delivering(); $file = Extension_MaxCache_Core::file_state(); if ( '' !== $file['reason'] ) { self::apply_to_file_in_the_way( $file, $exs ); } else { Extension_MaxCache_Core::refresh( $w3tc_config ); if ( Extension_MaxCache_Core::is_enabled( $w3tc_config ) ) { self::add( $w3tc_config, $exs ); } else { self::remove( $exs ); } } // W3TC's pass answered before this one ran, so a delivery that changed leaves its rules describing the moment before. if ( Extension_MaxCache_Core::is_delivering() !== $was_delivering ) { self::settle_pgcache_rules( $w3tc_config, $exs ); } } /** * Does what can be done about a rules file that stands in the way of this pass. * * @since 2.11.0 * * @param array $file What Extension_MaxCache_Core::file_state() answered. * @param object $exs Exception handler object. * * @return void */ private static function apply_to_file_in_the_way( $file, $exs ) { // Somebody else's block: ours can still come out, and serving beside theirs is what must not happen. if ( $file['foreign'] ) { self::remove( $exs ); return; } // Nothing may be written at all. Silence would leave the module serving from a block nobody can correct. if ( ! Extension_MaxCache_Core::is_delivering() ) { return; } $exs->push( new Util_WpFile_FilesystemModifyException( $file['reason'], // Null, not '': the collection adopts the first non-null form, and an empty one would hide a real one. null, $file['reason'], (string) Util_Rule::get_apache_rules_path() ) ); } /** * Has W3TC apply its own page-cache rules for the delivery this pass settled. * * @since 2.11.0 * * @param Config $w3tc_config W3TC Config containing relevant settings. * @param object $exs Exception handler object. * * @return void */ private static function settle_pgcache_rules( $w3tc_config, $exs ) { // Converted here, not by the callee: this is the one call that leaves the class, and the live file answers to the saved configuration. Dispatcher::component( 'PgCache_Environment' )->rules_apply_for_config( Extension_MaxCache_Core::saved_config( $w3tc_config ), $exs ); } /** * Adds the block. * * @since 2.11.0 * * @param Config $w3tc_config W3TC Config containing relevant settings. * @param object $exs Exception handler object. * * @return void */ private static function add( $w3tc_config, $exs ) { $path = Util_Rule::get_apache_rules_path(); // Keyed on whether NGINX is installed, not on whether it answers this request - cPanel fronts Apache with it. $notify = Extension_MaxCache_Core::nginx_module_installed(); $signature = $notify ? Extension_MaxCache_Configd::rules_file_signature( $path ) : ''; $before = \count( $exs->exceptions() ); $rules = Extension_MaxCache_Core::get_rules( $w3tc_config ); // An empty block puts add_rules() into removal mode, reporting nothing - the record would wrongly claim delivery. if ( '' === $rules ) { self::remove( $exs ); return; } Util_Rule::add_rules( $exs, $path, $rules, W3TC_MARKER_BEGIN_PGCACHE_MAXCACHE, W3TC_MARKER_END_PGCACHE_MAXCACHE, // Immediately before the WordPress block; no fallback anchor needed since position doesn't affect per-directory directives. array( W3TC_MARKER_BEGIN_WORDPRESS => 0 ) ); // On NGINX the block only takes effect once the daemon reads it - otherwise nothing serves while the record claims it does. $reached = $notify ? Extension_MaxCache_Configd::notify_if_changed( $path, $signature ) : true; // The record follows the file: an unreached daemon serves the previous config regardless of what was written. if ( $reached && \count( $exs->exceptions() ) === $before ) { Extension_MaxCache_Core::note_delivering( true ); } } /** * Takes the block out. * * @since 2.11.0 * * @param object $exs Exception handler object. * * @return void */ private static function remove( $exs ) { $path = Util_Rule::get_apache_rules_path(); // Same reasoning as add(): keyed on whether the NGINX build is installed, not on mode. $notify = Extension_MaxCache_Core::nginx_module_installed(); $signature = $notify ? Extension_MaxCache_Configd::rules_file_signature( $path ) : ''; $reported = \count( $exs->exceptions() ); Util_Rule::remove_rules( $exs, $path, W3TC_MARKER_BEGIN_PGCACHE_MAXCACHE, W3TC_MARKER_END_PGCACHE_MAXCACHE ); // Read back, not trusted: erase_rules() anchors on "\n", so a CRLF file keeps the block but reports success. // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $exists = $path && @\file_exists( $path ); $left = ( $exists && @\is_readable( $path ) ) ? @\file_get_contents( $path ) : false; // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, WordPress.PHP.NoSilencedErrors.Discouraged // Mirrors add(): until the daemon reads this, it keeps serving the old view, so the record must not hand rules back yet. $reached = $notify ? Extension_MaxCache_Configd::notify_if_changed( $path, $signature ) : true; // An unreadable file is unknown, not "gone" - reading it as gone would hand rules back with our block still there. $still_there = false === $left ? $exists : false !== \strpos( $left, W3TC_MARKER_BEGIN_PGCACHE_MAXCACHE ); // Unreadable branch only: telling the owner to delete markers from a file that may never have had them would never clear. $report = false !== $left || Extension_MaxCache_Core::is_delivering(); if ( $still_there && $report && \count( $exs->exceptions() ) === $reported ) { $exs->push( new Util_WpFile_FilesystemModifyException( \__( 'The MAx Cache block could not be removed.', 'w3-total-cache' ), // Null, not '': the collection adopts the first non-null form, and an empty one would hide a real one pushed later. null, \sprintf( /* translators: 1 path, 2 starting marker, 3 ending marker. */ \__( 'Edit file %1$s and remove all lines between and including %2$s and %3$s markers.', 'w3-total-cache' ), $path, W3TC_MARKER_BEGIN_PGCACHE_MAXCACHE, W3TC_MARKER_END_PGCACHE_MAXCACHE ), $path ) ); return; } if ( ! $still_there && $reached ) { Extension_MaxCache_Core::note_delivering( false ); } } }