paired_routing = $paired_routing;
}
/**
* Is Reader mode with a Reader theme selected.
*
* @param array $options Options to check. If omitted, the currently-saved options are used.
* @return bool Whether new Reader mode.
*/
public function is_enabled( $options = null ) {
// If the theme was overridden then we know it is enabled. We can't check get_template() at this point because
// it will be identical to $reader_theme.
if ( $this->is_theme_overridden() ) {
return true;
}
if ( null === $options ) {
$options = AMP_Options_Manager::get_options();
}
// If Reader mode is not enabled, then a Reader theme is definitely not going to be served.
if (
! isset( $options[ Option::THEME_SUPPORT ] )
||
AMP_Theme_Support::READER_MODE_SLUG !== $options[ Option::THEME_SUPPORT ]
) {
return false;
}
// If the Legacy Reader mode is active, then a Reader theme is not going to be served.
if ( ! isset( $options[ Option::READER_THEME ] ) ) {
return false;
}
$reader_theme = $options[ Option::READER_THEME ];
if ( ReaderThemes::DEFAULT_READER_THEME === $reader_theme ) {
return false;
}
// If the Reader theme does not exist, then we cannot switch to the reader theme. The Legacy Reader theme will
// be used as a fallback instead.
if ( ! wp_get_theme( $reader_theme )->exists() ) {
return false;
}
// Lastly, if the active theme is not the same as the reader theme, then we can switch to the reader theme.
// Otherwise, the site should instead be in Transitional mode. Note that get_stylesheet() is used as opposed
// to get_template() because the active theme should be allowed to be a child theme of a Reader theme.
return get_stylesheet() !== $reader_theme;
}
/**
* Whether the active theme was overridden with the reader theme.
*
* @return bool Whether theme overridden.
*/
public function is_theme_overridden() {
return $this->theme_overridden;
}
/**
* Register the service with the system.
*
* @return void
*/
public function register() {
// The following needs to run at plugins_loaded because that is when _wp_customize_include runs. Otherwise, the
// most logical action would be setup_theme.
add_action( 'plugins_loaded', [ $this, 'override_theme' ], 9 );
add_filter( 'wp_prepare_themes_for_js', [ $this, 'filter_wp_prepare_themes_to_indicate_reader_theme' ] );
add_action( 'admin_print_footer_scripts-themes.php', [ $this, 'inject_theme_single_template_modifications' ] );
}
/**
* Filter themes for JS to remove action to delete the selected Reader theme and show a notice.
*
* @param array $prepared_themes Array of theme data.
* @return array Themes.
*/
public function filter_wp_prepare_themes_to_indicate_reader_theme( $prepared_themes ) {
if ( ! $this->is_enabled() ) {
return $prepared_themes;
}
$reader_theme_obj = $this->get_reader_theme();
if ( ! $reader_theme_obj instanceof WP_Theme ) {
return $prepared_themes;
}
$reader_theme = $reader_theme_obj->get_stylesheet();
if ( isset( $prepared_themes[ $reader_theme ] ) ) {
// Make sure the AMP Reader theme appears right after the active theme in the list.
$stylesheet = get_stylesheet();
if ( isset( $prepared_themes[ $stylesheet ] ) ) {
$prepared_themes = array_merge(
[
$stylesheet => $prepared_themes[ $stylesheet ],
$reader_theme => $prepared_themes[ $reader_theme ],
],
$prepared_themes
);
}
// Prevent Reader theme from being deleted.
unset( $prepared_themes[ $reader_theme ]['actions']['delete'] );
// Make sure the Customize link goes to AMP.
$prepared_themes[ $reader_theme ]['actions']['customize'] = amp_get_customizer_url();
// Force the theme to be styled as Active.
$prepared_themes[ $reader_theme ]['active'] = true;
// Make sure the hacked Backbone template will use the AMP action links.
$prepared_themes[ $reader_theme ]['ampActiveReaderTheme'] = true;
// Add AMP Reader theme notice.
$prepared_themes[ $reader_theme ]['ampReaderThemeNotice'] = sprintf(
wp_kses(
/* translators: placeholder is link to AMP settings screen */
__( 'This has been selected as the AMP Reader theme.', 'amp' ),
[
'a' => [ 'href' => true ],
]
),
esc_url( add_query_arg( 'page', AMP_Options_Manager::OPTION_NAME, admin_url( 'admin.php' ) ) . '#reader-themes' )
);
}
return $prepared_themes;
}
/**
* Inject new logic into the Backbone templates for rendering a theme lightbox.
*
* This is admittedly hacky, but WordPress doesn't provide a much better option.
*/
public function inject_theme_single_template_modifications() {
if ( ! $this->is_enabled() ) {
return;
}
$reader_theme = $this->get_reader_theme();
if ( ! $reader_theme instanceof WP_Theme ) {
return;
}
?>
reader_theme instanceof WP_Theme ) {
return $this->reader_theme;
}
$reader_theme_slug = AMP_Options_Manager::get_option( Option::READER_THEME );
if ( ! $reader_theme_slug ) {
return null;
}
$reader_theme = wp_get_theme( $reader_theme_slug );
if ( $reader_theme->errors() ) {
return null;
}
return $reader_theme;
}
/**
* Get active theme.
*
* The theme that was active before switching to the Reader theme.
*
* @return WP_Theme|null
*/
public function get_active_theme() {
return $this->active_theme;
}
/**
* Switch theme if in Reader mode, a Reader theme was selected, and the AMP query var is present.
*
* Note that AMP_Theme_Support will redirect to the non-AMP version if AMP is not available for the query.
*
* @see WP_Customize_Manager::start_previewing_theme() which provides for much of the inspiration here.
* @see switch_theme() which ensures the new theme includes the old theme's theme mods.
*/
public function override_theme() {
$this->theme_overridden = false;
if ( ! $this->is_enabled() || ! $this->paired_routing->has_endpoint() ) {
return;
}
$theme = $this->get_reader_theme();
if ( ! $theme instanceof WP_Theme ) {
return;
}
$this->active_theme = wp_get_theme();
$this->reader_theme = $theme;
$this->theme_overridden = true;
$get_template = function () {
return $this->reader_theme->get_template();
};
$get_stylesheet = function () {
return $this->reader_theme->get_stylesheet();
};
add_filter( 'stylesheet', $get_stylesheet );
add_filter( 'template', $get_template );
add_filter(
'pre_option_current_theme',
function () {
return $this->reader_theme->display( 'Name' );
}
);
// @link: https://core.trac.wordpress.org/ticket/20027
add_filter( 'pre_option_stylesheet', $get_stylesheet );
add_filter( 'pre_option_template', $get_template );
// Handle custom theme roots.
add_filter(
'pre_option_stylesheet_root',
function () {
return get_raw_theme_root( $this->reader_theme->get_stylesheet(), true );
}
);
add_filter(
'pre_option_template_root',
function () {
return get_raw_theme_root( $this->reader_theme->get_template(), true );
}
);
$this->disable_widgets();
add_filter( 'customize_previewable_devices', [ $this, 'customize_previewable_devices' ] );
add_action( 'customize_register', [ $this, 'remove_customizer_themes_panel' ], 11 );
}
/**
* Disable widgets.
*/
public function disable_widgets() {
add_filter( 'sidebars_widgets', '__return_empty_array', PHP_INT_MAX );
add_filter(
'customize_loaded_components',
static function( $components ) {
return array_diff( $components, [ 'widgets' ] );
}
);
remove_theme_support( 'widgets-block-editor' );
}
/**
* Make tablet (smartphone) the default device when opening AMP Customizer.
*
* @param array $devices Devices.
* @return array Devices.
*/
public function customize_previewable_devices( $devices ) {
if ( isset( $devices['tablet'] ) ) {
unset( $devices['desktop']['default'] );
$devices['tablet']['default'] = true;
}
return $devices;
}
/**
* Remove themes panel from AMP Customizer.
*
* @param WP_Customize_Manager $wp_customize Customize manager.
*/
public function remove_customizer_themes_panel( WP_Customize_Manager $wp_customize ) {
$wp_customize->remove_panel( 'themes' );
}
}