|
Server : LiteSpeed System : Linux srv475744146.host.ultaserver.net 5.15.0-138-generic #148-Ubuntu SMP Fri Mar 14 19:05:48 UTC 2025 x86_64 User : indog6927 ( 1163) PHP Version : 8.0.30 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, Directory : /home/indogame99.org/public_html/wp-content/plugins/amp/src/ |
Upload File : |
<?php
/**
* Class MobileRedirection.
*
* @package AmpProject\AmpWP
*/
namespace AmpProject\AmpWP;
use AMP_HTTP;
use AMP_Options_Manager;
use AMP_Theme_Support;
use AmpProject\AmpWP\Infrastructure\Registerable;
use AmpProject\AmpWP\Infrastructure\Service;
use AmpProject\Html\Attribute;
/**
* Service for redirecting mobile users to the AMP version of a page.
*
* @package AmpProject\AmpWP
* @since 2.0
* @internal
*/
final class MobileRedirection implements Service, Registerable {
/**
* Regular expression for regular expressions. So meta.
*
* This must work in both PHP and cross-browser JS, which is why the 's' flag is not used. Also, this will get
* passed as the pattern argument to the RegExp constructor in JS, whereas in PHP it will be used as the pattern
* surrounded by the '#' delimiter.
*
* @var string
*/
const REGEX_REGEX = '^\/((?:.|\n)+)\/([i]*)$';
/**
* The name of the cookie or session storage key that persists the user's preference for viewing the non-AMP version of a page when on mobile.
*
* @var string
*/
const DISABLED_STORAGE_KEY = 'amp_mobile_redirect_disabled';
/**
* PairedRouting instance.
*
* @var PairedRouting
*/
private $paired_routing;
/**
* MobileRedirection constructor.
*
* @param PairedRouting $paired_routing Paired Routing.
*/
public function __construct( PairedRouting $paired_routing ) {
$this->paired_routing = $paired_routing;
}
/**
* Register.
*/
public function register() {
add_filter( 'amp_default_options', [ $this, 'filter_default_options' ] );
add_filter( 'amp_options_updating', [ $this, 'sanitize_options' ], 10, 2 );
if ( ! amp_is_canonical() ) {
$sandboxing_level = amp_get_sandboxing_level();
$is_mobile_redirect_enabled = AMP_Options_Manager::get_option( Option::MOBILE_REDIRECT );
// Add alternative link if mobile redirection is enabled or sandboxing level is set to loose or moderate.
if ( $is_mobile_redirect_enabled || ( 1 === $sandboxing_level || 2 === $sandboxing_level ) ) {
add_action( 'wp_head', [ $this, 'add_mobile_alternative_link' ] );
}
if ( ! $is_mobile_redirect_enabled ) {
add_action( 'template_redirect', [ $this, 'maybe_add_mobile_switcher_link' ], PHP_INT_MAX );
} else {
add_action( 'template_redirect', [ $this, 'redirect' ], PHP_INT_MAX );
// Enable AMP-to-AMP linking by default to avoid redirecting to AMP version when navigating.
// A low priority is used so that sites can continue overriding this if they have done so.
add_filter( 'amp_to_amp_linking_enabled', '__return_true', 0 );
add_filter( 'comment_post_redirect', [ $this, 'filter_comment_post_redirect' ] );
// Amend the comments/respond links to go to non-AMP page when in legacy Reader mode.
if ( amp_is_legacy() ) {
add_filter( 'get_comments_link', [ $this, 'add_noamp_mobile_query_var' ] ); // For get_comments_link().
add_filter( 'respond_link', [ $this, 'add_noamp_mobile_query_var' ] ); // For comments_popup_link().
}
}
}
}
/**
* Add default option.
*
* @param array $defaults Default options.
* @return array Defaults.
*/
public function filter_default_options( $defaults ) {
$defaults[ Option::MOBILE_REDIRECT ] = true;
return $defaults;
}
/**
* Sanitize options.
*
* @param array $options Existing options with already-sanitized values for updating.
* @param array $new_options Unsanitized options being submitted for updating.
*
* @return array Sanitized options.
*/
public function sanitize_options( $options, $new_options ) {
if ( isset( $new_options[ Option::MOBILE_REDIRECT ] ) ) {
$options[ Option::MOBILE_REDIRECT ] = rest_sanitize_boolean( $new_options[ Option::MOBILE_REDIRECT ] );
}
return $options;
}
/**
* Get the AMP version of the current URL.
*
* @return string AMP URL.
*/
public function get_current_amp_url() {
$url = $this->paired_routing->add_endpoint( amp_get_current_url() );
$url = remove_query_arg( QueryVar::NOAMP, $url );
return $url;
}
/**
* Add redirection logic if available for request.
*/
public function redirect() {
// If a site is AMP-first or AMP is not available for the request, then no redirection functionality will apply.
// Additionally, prevent adding redirection logic in the Customizer preview since that will currently complicate things.
if ( amp_is_canonical() || ! amp_is_available() ) {
return;
}
$js = $this->is_using_client_side_redirection();
if ( ! $js ) {
// If using server-side redirection, make sure that caches vary by user agent.
if ( ! headers_sent() ) {
header( 'Vary: User-Agent', false );
}
// Now abort if it's not an AMP page and the user agent is not mobile, since there won't be any redirection
// to the AMP version and we don't need to show a footer link to go to the AMP version.
if ( ! $this->is_mobile_request() && ! amp_is_request() ) {
return;
}
}
// Print the mobile switcher styles.
$this->add_mobile_switcher_head_hooks();
if ( ! amp_is_request() ) {
if ( $js ) {
// Add mobile redirection script.
add_action( 'wp_head', [ $this, 'add_mobile_redirect_script' ], ~PHP_INT_MAX );
} elseif ( ! $this->is_redirection_disabled_via_cookie() ) {
if ( $this->is_redirection_disabled_via_query_param() ) {
// Persist disabling mobile redirection for the session if redirection is disabled for the current request.
$this->set_mobile_redirection_disabled_cookie( true );
} else {
// Redirect to the AMP version since is_mobile_request and redirection not disabled by cookie or query param.
if ( wp_safe_redirect( $this->get_current_amp_url(), 302 ) ) {
exit;
}
}
}
// Add a link to the footer to allow for navigation to the AMP version.
$this->add_mobile_switcher_footer_hooks();
} else {
if ( ! $js && $this->is_redirection_disabled_via_cookie() ) {
$this->set_mobile_redirection_disabled_cookie( false );
}
$this->add_a2a_linking_hooks();
// Add a link to the footer to allow for navigation to the non-AMP version.
$this->add_mobile_switcher_footer_hooks();
}
}
/**
* Add mobile switcher link in footer when serving an AMP page.
*/
public function maybe_add_mobile_switcher_link() {
if ( amp_is_request() ) {
$this->add_mobile_switcher_head_hooks();
$this->add_mobile_switcher_footer_hooks();
}
}
/**
* Add mobile version switcher head hooks.
*/
private function add_mobile_switcher_head_hooks() {
add_action( 'wp_head', [ $this, 'add_mobile_version_switcher_styles' ] );
add_action( 'amp_post_template_head', [ $this, 'add_mobile_version_switcher_styles' ] ); // For legacy Reader mode theme.
}
/**
* Add mobile version switcher footer hooks.
*/
private function add_mobile_switcher_footer_hooks() {
add_action( 'wp_footer', [ $this, 'add_mobile_version_switcher_link' ] );
add_action( 'amp_post_template_footer', [ $this, 'add_mobile_version_switcher_link' ] ); // For legacy Reader mode theme.
}
/**
* Add AMP-to-AMP linking hooks.
*/
private function add_a2a_linking_hooks() {
add_filter( 'amp_to_amp_linking_element_excluded', [ $this, 'filter_amp_to_amp_linking_element_excluded' ], 100, 2 );
add_filter( 'amp_to_amp_linking_element_query_vars', [ $this, 'filter_amp_to_amp_linking_element_query_vars' ], 10, 2 );
}
/**
* Ensure that links/forms which point to ?noamp up-front are excluded from AMP-to-AMP linking.
*
* @param bool $excluded Excluded.
* @param string $url URL considered for exclusion.
* @return bool Element excluded from AMP-to-AMP linking.
*/
public function filter_amp_to_amp_linking_element_excluded( $excluded, $url ) {
if ( ! $excluded ) {
$query_string = wp_parse_url( $url, PHP_URL_QUERY );
if ( ! empty( $query_string ) ) {
$query_vars = [];
parse_str( $query_string, $query_vars );
$excluded = array_key_exists( QueryVar::NOAMP, $query_vars );
}
}
return $excluded;
}
/**
* Ensure that links/forms which point to ?noamp up-front are excluded from AMP-to-AMP linking.
*
* @param string[] $query_vars Query vars.
* @param bool $excluded Whether the element was excluded from AMP-to-AMP linking.
* @return string[] Query vars to add to the element.
*/
public function filter_amp_to_amp_linking_element_query_vars( $query_vars, $excluded ) {
if ( $excluded ) {
$query_vars[ QueryVar::NOAMP ] = QueryVar::NOAMP_MOBILE;
}
return $query_vars;
}
/**
* Determine if the current request is from a mobile device by looking at the User-Agent request header.
*
* This only applies if client-side redirection has been disabled.
*
* @return bool True if current request is from a mobile device, otherwise false.
*/
public function is_mobile_request() {
/**
* Filters whether the current request is from a mobile device. This is provided as a means to short-circuit
* the normal determination of a mobile request below.
*
* @since 2.0
*
* @param null $is_mobile Whether the current request is from a mobile device.
*/
$pre_is_mobile = apply_filters( 'amp_pre_is_mobile', null );
if ( null !== $pre_is_mobile ) {
return (bool) $pre_is_mobile;
}
if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
return false;
}
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___SERVER__HTTP_USER_AGENT__ -- Value is used only in pattern matching. Logic not used by default since requires amp_mobile_client_side_redirection filter opt-in.
$current_user_agent = wp_unslash( $_SERVER['HTTP_USER_AGENT'] );
$regex_regex = sprintf( '#%s#', self::REGEX_REGEX );
foreach ( $this->get_mobile_user_agents() as $user_agent_pattern ) {
if (
(
preg_match( $regex_regex, $user_agent_pattern ) // So meta!
&&
preg_match( $user_agent_pattern, $current_user_agent )
)
||
false !== strpos( $current_user_agent, $user_agent_pattern )
) {
return true;
}
}
return false;
}
/**
* Determine if mobile redirection should be done via JavaScript.
*
* If auto-redirection is disabled due to being in the Customizer preview or in AMP Dev Mode (and thus possibly in
* Paired Browsing), then client-side redirection is forced.
*
* @return bool True if mobile redirection should be done, false otherwise.
*/
public function is_using_client_side_redirection() {
if ( is_customize_preview() || Services::has( 'admin.paired_browsing' ) ) {
return true;
}
/**
* Filters whether mobile redirection should be done client-side (via JavaScript).
*
* If false, a server-side solution will be used instead (via PHP). It's important to verify that server-side
* redirection does not conflict with a site's page caching logic. To assist with this, you may need to hook
* into the `amp_pre_is_mobile` filter.
*
* Beware that disabling this will result in a cookie being set when the user decides to leave the mobile version.
* This may require updating the site's privacy policy or getting user consent for GDPR compliance. Nevertheless,
* since the cookie is not used for tracking this may not be necessary.
*
* Please note that this does not apply when in the Customizer preview or when in AMP Dev Mode (and thus possible
* Paired Browsing), since server-side redirects would not be able to be prevented as required.
*
* @since 2.0
*
* @param bool $should_redirect_via_js Whether JS redirection should be used to take mobile visitors to the AMP version.
*/
return (bool) apply_filters( 'amp_mobile_client_side_redirection', true );
}
/**
* Get a list of mobile user agents to use for comparison against the user agent from the current request.
*
* Each entry may either be a simple string needle, or it be a regular expression serialized as a string in the form
* of `/pattern/[i]*`. If a user agent string does not match this pattern, then the string will be used as a simple
* string needle for the haystack.
*
* @return string[] An array of mobile user agent search strings (and regex patterns).
*/
public function get_mobile_user_agents() {
// Default list compiled from the user agents listed in `wp_is_mobile()`.
$default_user_agents = [
'Mobile',
'Android',
'Silk/',
'Kindle',
'BlackBerry',
'Opera Mini',
'Opera Mobi',
];
/**
* Filters the list of user agents used to determine if the user agent from the current request is a mobile one.
*
* @since 2.0
*
* @param string[] $user_agents List of mobile user agent search strings (and regex patterns).
*/
return apply_filters( 'amp_mobile_user_agents', $default_user_agents );
}
/**
* Determine if mobile redirection is disabled via query param.
*
* @return bool True if disabled, false otherwise.
*/
public function is_redirection_disabled_via_query_param() {
return isset( $_GET[ QueryVar::NOAMP ] ) && QueryVar::NOAMP_MOBILE === wp_unslash( $_GET[ QueryVar::NOAMP ] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
}
/**
* Determine if mobile redirection is disabled via cookie.
*
* @return bool True if disabled, false otherwise.
*/
public function is_redirection_disabled_via_cookie() {
return isset( $_COOKIE[ self::DISABLED_STORAGE_KEY ] );
}
/**
* Sets a cookie to disable/enable mobile redirection for the current browser session.
*
* @param bool $add Whether to add (true) or remove (false) the cookie.
* @return void
*/
public function set_mobile_redirection_disabled_cookie( $add ) {
if ( $add ) {
$value = '1';
$expires = 0; // Time till expiry. Setting it to `0` means the cookie will only last for the current browser session.
// phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___COOKIE -- Cookies not used by default. Requires amp_mobile_client_side_redirection filter opt-in
$_COOKIE[ self::DISABLED_STORAGE_KEY ] = $value;
} else {
$value = null;
$expires = time() - YEAR_IN_SECONDS;
// phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___COOKIE -- Cookies not used by default. Requires amp_mobile_client_side_redirection filter opt-in
unset( $_COOKIE[ self::DISABLED_STORAGE_KEY ] );
}
if ( headers_sent() ) {
return;
}
$path = wp_parse_url( home_url( '/' ), PHP_URL_PATH ); // Path.
$secure = is_ssl(); // Whether cookie should be transmitted over a secure HTTPS connection.
$httponly = true; // Access via JS is unnecessary since cookie only get/set via PHP.
$samesite = 'strict'; // Prevents the cookie from being sent by the browser to the target site in all cross-site browsing context.
$domain = COOKIE_DOMAIN;
// phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.cookies_setcookie -- Cookies not used by default. Requires amp_mobile_client_side_redirection filter opt-in
setcookie(
self::DISABLED_STORAGE_KEY,
$value,
compact( 'expires', 'path', 'secure', 'httponly', 'samesite', 'domain' )
);
}
/**
* Output the mobile redirection Javascript code.
*/
public function add_mobile_redirect_script() {
$source = file_get_contents( __DIR__ . '/../assets/js/mobile-redirection.js' ); // phpcs:ignore WordPress.WP.AlternativeFunctions
$exports = [
'ampUrl' => $this->get_current_amp_url(),
'noampQueryVarName' => QueryVar::NOAMP,
'noampQueryVarValue' => QueryVar::NOAMP_MOBILE,
'disabledStorageKey' => self::DISABLED_STORAGE_KEY,
'mobileUserAgents' => $this->get_mobile_user_agents(),
'regexRegex' => self::REGEX_REGEX,
'isCustomizePreview' => is_customize_preview(),
'isAmpDevMode' => amp_is_dev_mode(),
];
$source = str_replace( 'AMP_MOBILE_REDIRECTION', wp_json_encode( $exports ), $source );
wp_print_inline_script_tag( $source );
}
/**
* Add rel=alternate link for AMP version.
*
* @link https://developers.google.com/search/mobile-sites/mobile-seo/separate-urls#annotation-in-the-html
*/
public function add_mobile_alternative_link() {
if ( amp_is_available() && ! amp_is_request() ) {
printf(
'<link rel="alternate" type="text/html" media="only screen and (max-width: 640px)" href="%s">',
esc_url( $this->get_current_amp_url() )
);
}
}
/**
* Redirect to AMP page after submitting comment if the URL is on this site.
*
* This avoids the need for a secondary redirect after having been redirected to the non-AMP URL.
*
* @param string $url URL.
* @return string Amended URL.
*/
public function filter_comment_post_redirect( $url ) {
if (
isset( AMP_HTTP::$purged_amp_query_vars[ AMP_HTTP::ACTION_XHR_CONVERTED_QUERY_VAR ] )
&&
wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( $url, PHP_URL_HOST )
) {
$url = $this->paired_routing->add_endpoint( $url );
}
return $url;
}
/**
* Add `?noamp=mobile` to a given URL.
*
* @param string $url URL.
* @return string Amended URL.
*/
public function add_noamp_mobile_query_var( $url ) {
return add_query_arg( QueryVar::NOAMP, QueryVar::NOAMP_MOBILE, $url );
}
/**
* Print the styles for the mobile version switcher.
*/
public function add_mobile_version_switcher_styles() {
/**
* Filters whether the default mobile version switcher styles are printed.
*
* @since 2.0
*
* @param bool $used Whether the styles are printed.
*/
if ( ! apply_filters( 'amp_mobile_version_switcher_styles_used', true ) ) {
return;
}
$source = file_get_contents( AMP__DIR__ . '/assets/css/amp-mobile-version-switcher' . ( is_rtl() ? '-rtl' : '' ) . '.css' ); // phpcs:ignore WordPress.WP.AlternativeFunctions
if ( 'twentytwentyone' === get_template() ) {
// When on a non-AMP page and the mobile menu is open, the mobile version link is incorrectly shown at the
// top of the page. In that case, the mobile version link is hidden.
$source .= '
body.lock-scrolling > #amp-mobile-version-switcher {
display: none;
}
';
}
printf( '<style>%s</style>', $source ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Output the link for the mobile version switcher.
*/
public function add_mobile_version_switcher_link() {
$should_redirect_via_js = $this->is_using_client_side_redirection();
$is_amp = amp_is_request();
if ( $is_amp ) {
$rel = [ Attribute::REL_NOFOLLOW ];
$url = add_query_arg( QueryVar::NOAMP, QueryVar::NOAMP_MOBILE, $this->paired_routing->remove_endpoint( amp_get_current_url() ) );
$text = __( 'Exit mobile version', 'amp' );
} else {
$rel = [];
$url = $this->get_current_amp_url();
$text = __( 'Go to mobile version', 'amp' );
}
/**
* Filters the text to be used in the mobile switcher link.
*
* Use the `amp_is_request()` function to determine whether you are filtering the
* text for the link to go to the non-AMP version or the AMP version.
*
* @since 2.0
*
* @param string $text Link text to display.
*/
$text = apply_filters( 'amp_mobile_version_switcher_link_text', $text );
if ( empty( $text ) ) {
return;
}
$hide_switcher = (
// The switcher must always be shown in the AMP version to allow accessing the non-AMP version.
! $is_amp
&&
// The switcher should be hidden if using client-side redirection since JS will determine if it is a mobile
// device and thus whether the switcher should be displayed.
$should_redirect_via_js
);
$container_id = 'amp-mobile-version-switcher';
?>
<div id="<?php echo esc_attr( $container_id ); ?>" <?php printf( $hide_switcher ? 'hidden' : '' ); ?>>
<a rel="<?php echo esc_attr( implode( ' ', $rel ) ); ?>" href="<?php echo esc_url( $url ); ?>">
<?php echo esc_html( $text ); ?>
</a>
</div>
<?php
// Note that the switcher link is disabled in Reader mode because there is a separate toggle to switch versions,
// and because there are controls which are AMP-specific which don't apply when switching between versions.
$is_amp_reader_customizer = (
is_customize_preview()
&&
AMP_Theme_Support::READER_MODE_SLUG === AMP_Options_Manager::get_option( Option::THEME_SUPPORT )
);
$is_possibly_paired_browsing = (
Services::has( 'admin.paired_browsing' )
&&
! is_customize_preview()
);
if ( $is_amp_reader_customizer || $is_possibly_paired_browsing ) :
$exports = [
'containerId' => $container_id,
'isReaderCustomizePreview' => $is_amp_reader_customizer,
'notApplicableMessage' => __( 'This link is not applicable in this context. It remains here for preview purposes only.', 'amp' ),
];
?>
<script data-ampdevmode>
(function( { containerId, isReaderCustomizePreview, notApplicableMessage } ) {
addEventListener( 'DOMContentLoaded', () => {
if ( isReaderCustomizePreview || [ 'paired-browsing-non-amp', 'paired-browsing-amp' ].includes( window.name ) ) {
const link = document.querySelector( `#${containerId} a[href]` );
link.style.cursor = 'not-allowed';
link.addEventListener( 'click', ( event ) => {
event.preventDefault();
event.stopPropagation();
alert( notApplicableMessage );
} );
}
} );
})( <?php echo wp_json_encode( $exports ); ?> );
</script>
<?php endif; ?>
<?php
}
}