*/ class Amp_WP_Public { /** * The ID of this plugin. * * @since 1.0.0 * @access private * @var string $plugin_name The ID of this plugin. */ private $plugin_name; /** * The version of this plugin. * * @since 1.0.0 * @access private * @var string $version The current version of this plugin. */ private $version; /** * Default endpoint for AMP URL of site. * this cna can overridden by filter * * @since 1.0.4 */ const SLUG = 'amp'; /** * Default Endpoint for AMP URL of Site. * This cna Can Be Overridden by Filter * * @var string * * @since 1.0.0 * */ const AMP_WP_STARTPOINT = self::SLUG; /** * pre_get_posts hook priority * * @var int * * @since 1.0.0 */ const AMP_WP_ISOLATE_QUERY_HOOK_PRIORITY = 100; /** * Store amp_wp_head action callbacks * * @see amp_wp_collect_and_remove_head_actions * * @var array * * @since 1.0.0 */ private $head_actions; /** * Store Array of Posts Id to Exlucde Transform Permalinks to Amp * * Array structure: array { * 'post id' => dont care, * ... * } * * @see amp_wp_transform_post_link_to_amp * * @var array * * @since 1.0.0 */ public $excluded_posts_id = array(); /** * Initialize the class and set its properties. * * @param string $plugin_name The name of the plugin. * @param string $version The version of this plugin. * @since 1.0.0 * */ public function __construct($plugin_name, $version) { $this->plugin_name = $plugin_name; $this->version = $version; // Registers the AMP Rewrite Rules add_filter('init', array($this, 'amp_wp_add_rewrite')); add_filter('init', array($this, 'amp_wp_append_index_rewrite_rule')); // Check Plugin Compatibility add_filter('template_redirect', array($this, 'amp_wp_plugins_compatibility')); // Initialize AMP Components add_filter('init', array($this, 'amp_wp_include_components')); // Changes Page Template File With AMP Template File add_action('template_include', array($this, 'amp_wp_include_template_file'), 9999); // Override Template File add_filter('comments_template', array($this, 'amp_wp_override_comments_template'), 9999); // Initialize AMP Theme and It’s Functionality add_action('after_setup_theme', array($this, 'amp_wp_template_functions'), 1); // Replace All Links Inside Contents to AMP Version. // Stops User to Go Outside of AMP Version. add_action('wp', array($this, 'amp_wp_replace_internal_links_with_amp_version')); // Registers All Components Scripts Into the Header Style and Scripts add_action('amp_wp_template_enqueue_scripts', array($this, 'amp_wp_enqueue_components_scripts')); // Let the Components to Do Their Functionality in Head add_action('amp_wp_template_head', array($this, 'amp_wp_trigger_component_head'), 0); // Collect All Output to Can Enqueue Only Needed Scripts and Styles in Pages. add_action('amp_wp_template_head', array($this, 'amp_wp_buffer_head_start'), 1); add_action('amp_wp_template_footer', array($this, 'amp_wp_buffer_head_end'), 999); // Collect and Rollback All Main Query Posts to Disable Thirdparty Codes to Change Main Query! // Action After 1000 Priority Can Work add_action( 'pre_get_posts', array( $this, 'amp_wp_isolate_pre_get_posts_start'), 1); add_action( 'pre_get_posts', array( $this, 'amp_wp_isolate_pre_get_posts_end'), self::AMP_WP_ISOLATE_QUERY_HOOK_PRIORITY); add_action('template_redirect', array($this, 'amp_wp_redirect_endpoint_url')); add_filter('redirect_canonical', array($this, 'amp_wp_fix_prevent_extra_redirect_single_pagination')); add_action('request', array($this, 'amp_wp_fix_search_page_queries')); add_filter('request', array($this, 'amp_wp_force_query_var_value')); add_action('parse_query', array($this, 'amp_wp_correct_query_when_is_front_page')); // Auto Redirect Mobile Users add_action('template_redirect', array($this, 'amp_wp_auto_redirect_to_amp'), 1); // Init AMP WP JSON-LD add_action('template_redirect', 'Amp_WP_Public::amp_wp_init_json_ld', 1); $this->amp_wp_fix_front_page_display_options(); } /** * Register the stylesheets for the admin area. * * @since 1.0.0 */ public function enqueue_styles() { } /** * Register the JavaScript for the admin area. * * @since 1.0.0 */ public function enqueue_scripts() { } /** * Callback: Add rewrite rules * Action: init * * @version 1.0.0 * @since 1.0.0 */ public function amp_wp_add_rewrite() { amp_wp_add_rewrite_startpoint(self::AMP_WP_STARTPOINT, EP_ALL); /** * "Automattic AMP" Plugin Compatibility */ $amp_qv = defined('AMP_WP_QUERY_VAR') ? AMP_WP_QUERY_VAR : 'amp'; add_rewrite_endpoint($amp_qv, EP_PERMALINK); } /** * Add a Rewrite Rule to Detect site.com/amp/ Requests * * @version 1.0.0 * @since 1.0.0 * * @return array */ public function amp_wp_append_index_rewrite_rule() { add_rewrite_rule(self::AMP_WP_STARTPOINT . '/?$', "index.php?amp=index", 'top'); } /** * Change most popular cache plugins in amp version to compatible with it * * @since 1.0.0 */ public function amp_wp_plugins_compatibility() { if (!is_amp_wp()) { return; } /** * W3 total cache */ add_filter('w3tc_minify_js_enable', '__return_false'); add_filter('w3tc_minify_css_enable', '__return_false'); /** * WP Rocket */ if (defined('WP_ROCKET_VERSION')) { if (!defined('DONOTMINIFYCSS')) { define('DONOTMINIFYCSS', true); } if (!defined('DONOTMINIFYJS')) { define('DONOTMINIFYJS', true); } // Disable WP Rocket lazy load add_filter('do_rocket_lazyload', '__return_false', PHP_INT_MAX); add_filter('do_rocket_lazyload_iframes', '__return_false', PHP_INT_MAX); // Disable HTTP protocol removing on script, link, img, srcset and form tags. remove_filter('rocket_buffer', '__rocket_protocol_rewrite', PHP_INT_MAX); remove_filter('wp_calculate_image_srcset', '__rocket_protocol_rewrite_srcset', PHP_INT_MAX); // Disable Concatenate Google Fonts add_filter('get_rocket_option_minify_google_fonts', '__return_false', PHP_INT_MAX); // Disable CSS & JS magnification add_filter('get_rocket_option_minify_js', '__return_false', PHP_INT_MAX); add_filter('get_rocket_option_minify_css', '__return_false', PHP_INT_MAX); } /** * WP Speed of Light * * https://wordpress.org/plugins-wp/wp-speed-of-light/ */ if (defined('WPSOL_VERSION')) { add_filter('wpsol_filter_js_noptimize', '__return_true', PHP_INT_MAX); add_filter('wpsol_filter_css_noptimize', '__return_true', PHP_INT_MAX); } /** * Lazy Load * https://wordpress.org/plugins/lazy-load/ */ if (class_exists('LazyLoad_Images')) { add_filter('lazyload_is_enabled', '__return_false', PHP_INT_MAX); } /** * Lazy Load XT * https://wordpress.org/plugins/lazy-load-xt/ */ if (class_exists('Image_Lazy_Load')) { global $lazyloadxt; if (is_object($lazyloadxt)) { remove_filter('the_content', array($lazyloadxt, 'filter_html')); remove_filter('widget_text', array($lazyloadxt, 'filter_html')); remove_filter('post_thumbnail_html', array($lazyloadxt, 'filter_html')); remove_filter('get_avatar', array($lazyloadxt, 'filter_html')); } } /* * * * Facebook Comments Plugin * https://wordpress.org/plugins/facebook-comments-plugin/ */ if (function_exists('fbcommentshortcode')) { remove_action('wp_footer', 'fbmlsetup', 100); remove_filter('the_content', 'fbcommentbox', 100); remove_filter('widget_text', 'do_shortcode'); } /* * * * Yoast SEO * https://wordpress.org/plugins/wordpress-seo/ */ if (defined('WPSEO_VERSION')) { if (class_exists('WPSEO_OpenGraph')) { add_action('amp_wp_template_head', array($this, 'amp_wp_yoast_seo_metatags_compatibility')); } if (is_home() && !amp_wp_is_static_home_page() && self::amp_wp_get_option('show_on_front') === 'page') { add_filter('pre_get_document_title', 'Amp_WP_Public::amp_wp_yoast_seo_homepage_title', 99); } if (is_home()) { add_filter('amp_wp_json_ld_website', 'Amp_WP_Public::amp_wp_yoast_seo_homepage_json_ld'); } } /* * * * Ultimate Tweaker * https://ultimate-tweaker.com/ */ if (class_exists('ultimate_tweaker_Plugin_File') && defined('UT_VERSION')) { amp_wp_remove_class_filter('post_thumbnail_html', 'OT_media_image_no_width_height_Tweak', '_do', 10); amp_wp_remove_class_filter('image_send_to_editor', 'OT_media_image_no_width_height_Tweak', '_do', 10); } /** * WPO Tweaks * * https://servicios.ayudawp.com/ */ if (function_exists('wpo_tweaks_init')) { remove_filter('script_loader_tag', 'wpo_defer_parsing_of_js'); } } /** * Include Active Template functions.php File If Exits * * Callback: include * action: after_setup_theme * * @since 1.0.0 */ public function amp_wp_template_functions() { if (file_exists(AMPWP_TEMPLATE_DIR_PATH . 'includes/functions/amp-wp-template-functions.php')) { require_once AMPWP_TEMPLATE_DIR_PATH . 'includes/functions/amp-wp-template-functions.php'; } } /** * Callback: Include registered AMP components * Action: init * * @since 1.0.0 */ public function amp_wp_include_components() { include AMPWP_TEMPLATE_DIR_PATH . 'includes/components/class-amp-wp-img-component.php'; include AMPWP_TEMPLATE_DIR_PATH . 'includes/components/class-amp-wp-iframe-component.php'; include AMPWP_TEMPLATE_DIR_PATH . 'includes/components/class-amp-wp-carousel-component.php'; } /** * Callback: Include AMP template file in AMP pages * Action: template_include * * @param string $template_file_path Original Template File Path * * @access public * @version 1.0.0 * @since 1.0.0 * * @return string */ public function amp_wp_include_template_file($template_file_path) { if( !is_amp_wp() ) { return $template_file_path; } $include = $this->amp_wp_template_loader(); if ($include) { return $include; } } /** * Load a Template * * Handles Template Usage so That We Can Use Our Own Templates Instead of the Themes * * Templates are in the 'templates' folder. amp-wp looks for theme. * overrides in /yourtheme/amp-wp/ by default. * * @see Template Hierarchy Reference https://developer.wordpress.org/files/2014/10/wp-hierarchy.png * @param mixed $template * @return string */ public function amp_wp_template_loader() { if (amp_wp_is_static_home_page()) { $template = amp_wp_static_home_page_template(); $this->amp_wp_set_page_query(apply_filters('amp_wp_template_page_on_front', 0)); } elseif (is_home()) { $template = amp_wp_home_template(); } elseif (is_post_type_archive()) { $template = amp_wp_archive_post_type_template(); } elseif (is_archive()) { $template = amp_wp_archive_template(); } elseif (is_attachment()) { $template = amp_wp_attachment_template(); remove_filter('the_content', 'prepend_attachment'); } elseif (is_single()) { $template = amp_wp_single_template(); } elseif (is_page()) { $template = amp_wp_page_template(); } elseif (is_404()) { $template = amp_wp_404_template(); } elseif (is_search()) { $template = amp_wp_search_template(); } elseif (is_embed()) { return $template; } else { $template = amp_wp_index_template(); } return $template; } /** * Replace amp comment file with theme file * * @param string $file * * @since 1.0.0 * @return string */ public function amp_wp_override_comments_template($file) { if (is_amp_wp()) { if ($path = amp_wp_locate_template(basename($file))) { return $path; } } return $file; } /** * Replaces All Website Internal Links With AMP Version * * @hooked wp * * @param WP $wp * * @since 1.0.0 * @return string */ public function amp_wp_replace_internal_links_with_amp_version($wp) { if (empty($wp->query_vars['amp'])) { return; } add_filter('nav_menu_link_attributes', array('Amp_WP_Content_Sanitizer', 'replace_href_with_amp')); add_filter('the_content', array('Amp_WP_Content_Sanitizer', 'transform_all_links_to_amp')); add_filter('author_link', array('Amp_WP_Content_Sanitizer', 'transform_to_amp_url')); add_filter('term_link', array('Amp_WP_Content_Sanitizer', 'transform_to_amp_url')); add_filter('post_link', array($this, 'amp_wp_transform_post_link_to_amp'), 20, 2); add_filter('page_link', array($this, 'amp_wp_transform_post_link_to_amp'), 20, 2); add_filter('attachment_link', array('Amp_WP_Content_Sanitizer', 'transform_to_amp_url')); add_filter('post_type_link', array('Amp_WP_Content_Sanitizer', 'transform_to_amp_url')); } /** * Transform allowed posts URL to AMP * * @param string $url The Post’s Permalink * @param WP_Post|int $post The post object/id of the post. * * @since 1.0.0 * @return string */ public function amp_wp_transform_post_link_to_amp($url, $post) { $post_id = isset($post->ID) ? $post->ID : $post; if (isset($this->excluded_posts_id[$post_id])) { return $url; } return Amp_WP_Content_Sanitizer::transform_to_amp_url($url); } /** * Append AMP Components JavaScript If AMP Version Requested * * @access public * @version 1.0.0 * @since 1.0.0 */ public function amp_wp_enqueue_components_scripts() { $deps = array('ampproject'); amp_wp_enqueue_script($deps[0], 'https://cdn.ampproject.org/v0.js'); // Enqueues all needed scripts of components with 'ampproject' dependency $this->amp_wp_call_components_method('enqueue_amp_scripts', $deps); if (current_theme_supports('amp-wp-navigation') && current_theme_supports('amp-wp-has-nav-child')) { amp_wp_enqueue_script('amp-accordion', 'https://cdn.ampproject.org/v0/amp-accordion-0.1.js'); } if (current_theme_supports('amp-wp-form')) { amp_wp_enqueue_script('amp-form', 'https://cdn.ampproject.org/v0/amp-form-0.1.js'); } } /** * Callback: Fire head method of component for following purpose: * 1) Component able to add_filter or add_action if needed * 2) Create fresh instance of each component and cache * * Action: amp_wp_template_head * * @version 1.0.0 * @since 1.0.0 */ public function amp_wp_trigger_component_head() { $this->amp_wp_call_components_method('head'); } /** * Callback: Starts the Collecting Output to Enable Components to Add Style Into Head * Print Theme Completely Then Fire amp_wp_head() Callbacks and Append It Before * * Action: amp_wp_template_head * * @see amp_wp_buffer_head_end * * @version 1.0.0 * @since 1.0.0 */ public function amp_wp_buffer_head_start() { remove_action(current_action(), array($this, __FUNCTION__), 1); $this->amp_wp_collect_and_remove_head_actions(); ob_start(); } /** * Collect amp_wp_head actions and remove those actions * * @see amp_wp_head * * @since 1.0.0 */ public function amp_wp_collect_and_remove_head_actions() { $actions = &$GLOBALS['wp_filter']['amp_wp_template_head']; $this->head_actions = $actions; $actions = array(); } /** * Callback: Fire amp_wp_head() and print buffered output * Action: amp_wp_template_head * * @see amp_wp_buffer_head_start * * @version 1.0.0 * @since 1.0.0 */ public function amp_wp_buffer_head_end() { $content = ob_get_clean(); $prepend = ''; if (!amp_wp_is_customize_preview()) { $prepend .= ''; /** * Convert Output to Valid AMP HTML */ $instance = new Amp_WP_Html_Util(); $instance->loadHTML('
' . $content . '', null, false); preg_match('#(<\s*body[^>]*>)#isx', $content, $match); $prepend .= isset($match[1]) ? $match[1] : ''; // open body tag $this->amp_wp_render_content($instance, true); // Convert HTML top amp html // @see Amp_WP_Component::enqueue_amp_tags_script $this->amp_wp_call_components_method('enqueue_amp_tags_script', $instance); $content = $instance->get_content(true); // End convert output to valid amp html } $GLOBALS['wp_filter']['amp_wp_template_head'] = $this->head_actions; $this->head_actions = array(); do_action('amp_wp_template_head'); echo $prepend, $content; } /** * Transforms HTML Content to AMP Content * * todo: Add File Caching * * @param Amp_WP_Html_Util $instance * @param boolean $sanitize * * @since 1.0.0 */ public function amp_wp_render_content(Amp_WP_Html_Util $instance, $sanitize = false) { $this->amp_wp_call_components_method('render', $instance); if ($sanitize) { $sanitizer = new Amp_WP_Content_Sanitizer($instance); $sanitizer->sanitize(); } } /** * Fire specific method of all components * * @param string $method_name component method * * @param mixed $param * * @return mixed * @since 1.0.0 * */ public function amp_wp_call_components_method($method_name, $param = null) { global $amp_wp_registered_components; if (!$amp_wp_registered_components) { return $param; } // collect and prepare method arguments $args = func_get_args(); $args = array_slice($args, 1); if (!isset($args[0])) { $args[0] = null; } // iterate registered components and call method on them foreach ($amp_wp_registered_components as $component) { $instance = Amp_WP_Component::instance($component['component_class']); if ($this->_amp_wp_can_call_component_method($instance, $method_name, $args)) { $args[0] = call_user_func_array(array($instance, $method_name), $args); } } return $args[0]; } /** * Determines that method exists and is callable on object instance * * @param Amp_WP_Component $instance Live object of Amp_WP_Component * @param string $method_name Method of object * @param array $args * * @access private * @since 1.0.0 * * @return bool */ private function _amp_wp_can_call_component_method(&$instance, &$method_name, &$args) { $return = is_callable(array($instance, $method_name)); switch ($method_name) { case 'enqueue_amp_scripts': $return = $return && $instance->can_enqueue_scripts(); break; } return $return; } /** * Callback: Prevent Third Party Codes to Change the Main Query on AMP Version * * You Can Add Action To 'pre_get_posts' With Priority Grater Than 1000 to Change It. * * Action: pre_get_posts * * @see amp_wp_isolate_pre_get_posts_end * * @param WP_Query $wp_query * * @access public * @version 1.0.0 * @since 1.0.0 */ public function amp_wp_isolate_pre_get_posts_start($wp_query) { global $amp_wp_isolate_pre_get_posts; if (is_amp_wp($wp_query) && !is_admin() && $wp_query->is_main_query()) { $amp_wp_isolate_pre_get_posts = $wp_query->query_vars; } } /** * Rollback the Main Query Vars. * * @see 'amp_wp_isolate_pre_get_posts_end' for more documentation * * Action: pre_get_posts * @version 1.0.0 * @since 1.0.0 * * @param WP_Query $wp_query */ public function amp_wp_isolate_pre_get_posts_end(&$wp_query) { global $amp_wp_isolate_pre_get_posts; if (is_amp_wp($wp_query) && !is_admin() && $wp_query->is_main_query()) { if ($amp_wp_isolate_pre_get_posts) { $wp_query->query_vars = $amp_wp_isolate_pre_get_posts; unset($amp_wp_isolate_pre_get_posts); } } } /** * Handy fix for changing search query * * @param mixed $q * * @access public * @version 1.0.0 * @since 1.0.0 * * @return mixed */ public function amp_wp_fix_search_page_queries($q) { if (!empty($q['amp']) && !empty($q['s'])) { $q['post_type'] = array('post'); } return $q; } /** * Make sure the `amp` query var has an explicit value. * * @param int $query_vars * @since 1.0.0 * * @return int */ public function amp_wp_force_query_var_value($query_vars) { if (isset($query_vars[self::AMP_WP_STARTPOINT]) && '' === $query_vars[self::AMP_WP_STARTPOINT]) { $query_vars[self::AMP_WP_STARTPOINT] = 1; } return $query_vars; } /** * Fix up WP_Query for front page when amp query var is present. * * Normally the front page would not get served if a query var is present other than preview, page, paged, and cpage. * * @since 1.0.0 * @see WP_Query::parse_query() * @link https://github.com/WordPress/wordpress-develop/blob/0baa8ae85c670d338e78e408f8d6e301c6410c86/src/wp-includes/class-wp-query.php#L951-L971 * * @param WP_Query $query Query. */ public function amp_wp_correct_query_when_is_front_page(WP_Query $query) { $is_front_page_query = ( $query->is_main_query() && $query->is_home() && // Is AMP endpoint. false !== $query->get(self::AMP_WP_STARTPOINT, false) && // Is query not yet fixed uo up to be front page. !$query->is_front_page() && // Is showing pages on front. 'page' === get_option('show_on_front') && // Has page on front set. get_option('page_on_front') && // See line in WP_Query::parse_query() at