=== AMP WP - Google AMP For WordPress - Full Changelog ===

This file is the complete version history for AMP WP. The "Changelog" section
in readme.txt lists only the most recent releases (to stay within the
WordPress.org 5,000-word readme limit); every release ever shipped is recorded
here in full detail.

Entry convention (applied to new releases going forward): bullets are grouped
under category headers in this order - Features, Security, Fixed, Improvements,
WordPress Compatibility (Compat), Refactored.

== Changelog ==

= 1.8.3 - 2026-06-15 =
* Fix: JSON-LD structured data now uses canonical (non-AMP) URLs for mainEntityOfPage, NewsArticle url, and author url instead of the AMP endpoint URLs that Google Search Console flagged as mismatched.
* Fix: JSON-LD description no longer concatenates words when HTML tags are stripped (e.g. "thatpeople" is now "that people").
* Fix: Author object in JSON-LD now includes the `url` property pointing to the author archive page, satisfying Google's structured data recommendation.
* Fix: SearchAction urlTemplate in WebSite and WebPage schemas now uses the correct `home_url('/?s={search_term_string}')` format instead of the malformed double-path `get_search_link() . '?s=...'`.
* Fix: Sanitizer now preserves amp-bind bracket attributes ([src], [hidden], [class], [text], etc.) on elements with tag rules, preventing them from being stripped during STEP 8 attribute filtering.
* Fix: Added `amp-state` to the allowed protocol list for amp-list `src`, so `src="amp-state:..."` no longer triggers element removal.
* Fix: Added `hidden` to the general attributes whitelist so the HTML5 `hidden` attribute is preserved on all elements during sanitization.
* Fix: Social share links multi-select in Layout settings was double-escaping the `selected` attribute via `esc_attr()`, potentially preventing saved selections from being restored. Same bug fixed in General settings post types and taxonomies multi-selects.
* Fix: `submit_button()` text argument was wrapped in `esc_html__()` across all settings tabs, causing double-escaping since `submit_button()` escapes internally. Replaced with `__()`.
* Fix: 11 missing translation defaults for comment-related keys in `amp_wp_translation_stds()`, causing empty strings on the front-end when the Translation settings page was never saved.
* Fix: Translation settings page was missing the sidebar header field and the WooCommerce translation section.
* Fix: Customizer class `_x()` call had the textdomain in the context parameter slot.
* Fix: Textarea fields for exclude URLs and GDPR consent message used `wp_kses_post()` instead of `esc_textarea()` inside `<textarea>` elements.
* Fix: Theme name label in General settings used `esc_attr()` instead of `esc_html()` for text content inside a `<label>`.
* Improvement: Settings page header/footer bars now align with the sidebar menu item height (flexbox layout, matched padding, corrected button line-height).
* Refactor: Full WPCS/Intelephense cleanup of all 8 Settings admin templates (General, Layout, Social Links, Analytics, Translation, Notice Bar, GDPR, Third-Party Plugins, Premium Extensions).
* Refactor: Full WPCS cleanup of `sanitizer-rules.php`, `tags-list.php`, and `class-amp-wp-content-sanitizer.php` (82 errors + 25 warnings to 0/0).
* Refactor: WPCS cleanup of 6 Customizer control classes: file docblocks, PHPDoc annotations, hardcoded version strings replaced with `AMP_WP_VERSION`.
* Refactor: Replaced `urlencode()` with `rawurlencode()` in the Customizer class.
* Refactor: Renamed `AMPWP_WOOCOMMERCE_VERSION` constant to `AMP_WP_WOOCOMMERCE_VERSION` in the WooCommerce add-on for naming consistency.

= 1.8.2 - 2026-06-12 =
* Security: Added `current_user_can( 'manage_options' )` capability check to the settings save dispatcher. Previously only nonce verification protected the save path.
* Security: All settings values saved via `filter_input_array( INPUT_POST )` are now sanitized through `wp_kses_post()` before storage across all 9 settings classes (General, Layout, Social Links, Analytics, Translation, Notice Bar, GDPR, Structured Data, Third-Party Plugins). Safe HTML (links, bold, emphasis) used in GDPR consent text and copyright fields is preserved; dangerous HTML (script, iframe, event handlers) is stripped. Previously raw POST values were stored directly.
* Security: Fixed missing `sanitize_text_field` on the Google Analytics tracking ID before storage.
* Security: Sanitized the `AMP-Redirect-To` response header in the consent submission AJAX handler with `esc_url_raw()` to prevent header injection via a crafted Referer.
* Security: Added AMP CORS origin validation to the `amp_consent_submission` AJAX handler. The handler now verifies the `Origin` / `AMP-Same-Origin` header and `__amp_source_origin` parameter before responding, preventing cross-origin request forgery. Also adds the required `Access-Control-Expose-Headers` response header per the AMP CORS spec.
* Security: Escaped OneSignal subscribe/unsubscribe button labels with `esc_html()`. The translation strings were echoed without escaping inside `<button>` elements on the public frontend.
* Security: Sanitized direct `$_GET` and `$_COOKIE` access in the mobile auto-redirect skip logic with `sanitize_text_field( wp_unslash() )`.
* Security: Added `if ( ! defined( 'ABSPATH' ) ) exit;` direct-access guard to all PHP files that were missing it (admin partials, public templates, class files, function files, index files).
* Security: Switched social share count API calls from HTTP to HTTPS (Facebook, Pinterest, Reddit). Removed deprecated Twitter/newsharecounts and StumbleUpon endpoints. Removed `sslverify => false` from remote request args. Added `esc_url_raw()` on the URL parameter and `(int)` cast on returned counts.
* Fix: System Status "Get System Report" button produced an empty textarea. Rewrote the report generation: the plain-text report is now built server-side in PHP (Amp_WP_System_Status::get_system_report()) and passed to the frontend as a JS variable, replacing the fragile client-side jQuery DOM-parsing logic that scraped table cells. Also added tiptip-jquery-plugin as a script dependency and guarded both tipTip() calls so tooltip failure can never block other admin functionality.
* Fix: System Status GET connectivity check was incorrectly evaluating `$post_response` instead of `$get_response`, causing the GET test to always mirror the POST result.
* Fix: the content sanitizer stripped the AMP global attributes `placeholder`, `fallback`, and `overflow` from regular elements, turning amp-list / amp-iframe loading placeholders and error fallbacks into permanently visible static content (e.g. the WooCommerce AMP cart showed "Unable to load your cart" even after the cart loaded - see the `amp-wp-woocommerce` changelog). These attributes are now whitelisted alongside `submit-success` / `submit-error`.
* Fix: the `amp-list` sanitizer rule predated several spec attributes and stripped them; `items`, `single-item`, `max-items`, `binding`, `reset-on-refresh`, and `xssi-prefix` are now allowed. Stripping `items` silently forced every amp-list onto the default `items` JSON key, breaking any list bound to a different key (e.g. the cart totals list reading `items="totals"`).
* Fix: the content sanitizer removed the JSON payload (`<script type="application/json">`) inside `<amp-state>` elements - its script-removal pass only kept JSON scripts whose parent is `amp-analytics`, `amp-geo`, or `amp-consent`. Every `amp-state` therefore rendered empty, and any amp-bind interaction reading that state silently broke (e.g. the WooCommerce variable-product add-to-cart on AMP single pages - see the `amp-wp-woocommerce` changelog). `amp-state` is now included in the allowed-parent list for JSON scripts.

= 1.8.1 - 2026-06-10 =
* i18n: Community-driven translations are now documented and supported via translate.wordpress.org, local .po/.mo files, and the built-in Translation Panel. Updated the .pot file and added contributor instructions in the readme.
* Compat: Added two compatibility action hooks inherited from other AMP plugins (`ampforwp_after_header` in the header template and `amp_post_template_css` in the custom-styles buffer). These are not part of the AMP WP public API; they allow third-party plugins that target the AMPforWP hook surface to inject content and CSS on AMP WP pages without forking core.
* The plugin update notice on the Plugins page is now a branded, visually distinct card with a version badge, checkmark bullet list, and clear layout.

= 1.8.0 - 2026-05-30 =
* Feature: New "Hide Title in Single" toggle in Settings > Layout > Single Post Page. When enabled, the post title (`<h1 class="post-title">`) is omitted on AMP single-post pages. Off by default and backward compatible: the toggle is an inverted "hide" flag (`amp_wp_layout_settings['hide_title_in_single']`) rather than a "show" flag, because the layout save mechanism stores checked boxes as `1` and omits unchecked ones - an absent value therefore means "show", preserving the historical always-on behaviour for sites that upgrade without re-saving. Implemented in three layers: default/load/save of the new key in `Amp_WP_Layout` (`includes/admin/settings/class-amp-wp-layout.php`), the checkbox row in `admin/partials/settings/amp-wp-admin-layout.php` (placed right after "Show Thumbnail", matching display order), and an early-return guard in the `single-post/title.php` template (bumped to v1.1.0). The browser-tab title and JSON-LD structured data are unaffected, so SEO is preserved.
* AMP-Validity: 2026-05-28 - Added CSS value sanitizer (`Amp_WP_Styles::sanitize_css_values()`) that rounds decimal `rgb()`/`rgba()` values to integers and strips CSS declarations using the `attr()` function with `type()` syntax (CSS Level 5). Fixes the "CSS syntax error in tag 'style amp-custom'" AMP validation error caused by WordPress 7.0 view-transitions CSS and Dart Sass compiled stylesheets with fractional color values.
* Refactor: WPCS cleanup of `includes/admin/settings/class-amp-wp-layout.php` - added the missing class docblock (`@since 1.4.0`) and terminated four section-marker inline comments ("Single Post Page - End", "Header Settings - End", "Footer Settings - End", "Layout Settings - Home Page - End") with full stops. File now passes `phpcs --standard=WordPress` with exit 0. No behaviour change.
* Refactor: WPCS cleanup of `admin/partials/settings/amp-wp-admin-layout.php` - added the required blank line after the file docblock; supplied the `amp-wp` text domain on the two "Classic View" / "List View" option labels (previously `esc_html_e( ..., '' )` with an empty domain, flagged on lines 105/106/156/157); and resolved all 19 loose-comparison warnings + three Yoda errors by type. The eight select-dropdown comparisons (home/archive layout, share-count, share-link-format, related-posts algorithm) now use `===` against their string option values; the four parent-child toggle "active" checks (`is_show_sidebar`, `slider_on_home`, `social_share_on_post`, `show_related_posts`) were rewritten as `! empty( $var )` rather than `===` - the layout save handler stores checked boxes as integer `1`, so a strict `1 === '1'` would always be false and the "active" CSS class (and its expanded child-field panel) would never apply. File now passes `phpcs --standard=WordPress` with exit 0. No behaviour change.
* Refactor: WPCS cleanup of `admin/partials/settings/amp-wp-admin-general.php` - switched the two AMP URL Format `<select>` comparisons (`'start-point'` / `'end-point'` against `$url_structure`, a string-valued option) to strict `===`. Both are string compares so `===` is safe here (no int-toggle involved). Also defaulted the ten variables the view receives from `Amp_WP_General::amp_wp_add_general_settings()` (`$theme_name`, `$amp_on_home`, `$amp_on_search`, `$amp_on_post_types`, `$amp_on_taxonomies`, `$exclude_urls`, `$excluded_urls`, `$mobile_auto_redirect`, `$url_structure`, `$google_auto_ads`) via `??` against the controller's own default values, so the partial is valid when analysed standalone and Intelephense no longer reports them as undefined; no runtime change since the controller always sets them. File now passes `phpcs --standard=WordPress` with exit 0. No behaviour change.
* Refactor: WPCS cleanup of five Settings tab classes. `class-amp-wp-general.php` and `class-amp-wp-premium-extensions.php` gained a class docblock. `class-amp-wp-social-links.php`, `class-amp-wp-structured-data.php`, and `class-amp-wp-third-party-plugins-support.php` each had their `ABSPATH` guard (previously inlined on the opening `<?php` tag, which prevented the docblock from being recognised as the file comment) moved below a proper file docblock, and each gained a class docblock. Additional fixes: structured-data - three inline-comment full stops and one `@param` full stop; social-links - two inline-comment full stops; third-party - corrected the copy-pasted "Structured Data setting" summary to "Third Party Plugins Support setting". All five now pass `phpcs --standard=WordPress` with exit 0. No behaviour change.
* Refactor: WPCS cleanup of `includes/admin/settings/class-amp-wp-translation.php` - moved the `ABSPATH` guard below a proper file docblock and added a class docblock, terminated three inline comments and one `@param` with full stops, and added the required `translators:` comments above the placeholder-bearing strings "Reply To %s" (×3) and "Page %1$s of %2$s" (×3) inside the `amp_wp_comment_fields` array. These are genuine i18n fixes (translators now have placeholder context), not just style. File now passes `phpcs --standard=WordPress` with exit 0. No behaviour change.
* Refactor: WPCS cleanup of `includes/admin/settings/class-amp-wp-analytics.php` - added the missing class docblock (`@since 1.0.4`); the file docblock and `ABSPATH` guard were already well-formed. File now passes `phpcs --standard=WordPress` with exit 0. No behaviour change.
* Docs: fixed a malformed `@param` PHPDoc block in `includes/admin/class-amp-wp-settings.php`. The `amp_wp_settings_tab_menus` filter docblock used invalid `@param array (){ ... @type array Tab Id => Settings Tab Name }` notation (no variable name, `(){` instead of the hash-notation ` {`), which static analysers could not parse; rewritten as a valid `@param array $settings_tabs { ... @type string $tab_id ... }` block. Also corrected an `@Since` -> `@since` tag typo. phpcs was already clean on this file; no behaviour change. The identical malformed block on the `amp_wp_welcome_tab_menus` filter in `includes/admin/class-amp-wp-welcome.php` was rewritten the same way (to `@param array $welcome_tabs { ... }`).
* Refactor: WPCS cleanup of `includes/admin/class-amp-wp-help.php` - added the missing class docblock (`@since 1.2.0`) and reworded the `// callable $function.` argument comment (which the commented-out-code sniff flagged) to plain prose. File now passes `phpcs --standard=WordPress` with exit 0. No behaviour change.
* Refactor: WPCS cleanup of three Welcome-page tab classes - `includes/admin/welcome/class-amp-wp-credits.php`, `class-amp-wp-features.php`, and `class-amp-wp-getting-started.php` - each gained the missing class docblock (`@since 1.4.0`). All three now pass `phpcs --standard=WordPress` with exit 0. No behaviour change.
* Removed: deleted `includes/admin/class-amp-wp-system-status-override-function.php`, a dead, never-`require`d duplicate of the active `includes/admin/class-amp-wp-system-status.php`. Both declared `class Amp_WP_System_Status` and both ran `new Amp_WP_System_Status();`, so loading the orphan alongside the real file would fatal with "Cannot redeclare class"; only the correctly-named file is wired up (via `class-amp-wp-admin.php`). The orphan also still contained the old WooCommerce-coupled `get_theme_info()` template-scan code (`WC()`, `WC_Admin_Status`) that the active file had already dropped, so it doubled as a family-isolation violation. While there, finished WPCS cleanup of the active `class-amp-wp-system-status.php`: added the missing class docblock, reworded a trailing comment that the commented-out-code sniff flagged, and removed an unnecessary `@` error-suppression on `ini_get( 'memory_limit' )`. Active file now passes `phpcs --standard=WordPress` with exit 0. No behaviour change.

= 1.7.11 - 2026-05-25 =

* AMP-Validity: three-layer defense for invalid CSS at-rule stripping, resolving the `@view-transition` AMP validation error on sites running WordPress 7.0 with View Transitions enabled in global styles. (1) The content sanitizer now extracts `<style>` tags from `<head>` (not just `<body>`), feeding their CSS through `amp_wp_add_inline_style()` — this catches WordPress global-styles that previously bypassed the body-only extraction and appeared unsanitized in the AMP output. (2) Centralized the at-rule stripping regex into `Amp_WP_Styles::strip_invalid_atrules()` static method, called on both input (`amp_wp_add_inline_style()`) and output (`Amp_WP_Styles::print_inline_styles()`). (3) The static method returns empty string on PCRE backtrack-limit failure rather than passing through unsanitized CSS.

= 1.7.10 - 2026-05-25 =

* AMP-Validity: strip CSS at-rules not allowed in AMP `<style amp-custom>` (e.g. `@view-transition`, `@import`, `@charset`, `@namespace`, `@layer`), including when nested inside `@media` or `@supports` blocks. WordPress 7.0 adds `@media (prefers-reduced-motion:no-preference){@view-transition{navigation:auto}...}` to its global-styles-inline-css output; the initial top-level-only regex missed `@view-transition` inside the `@media` wrapper. The sanitizer now runs a second pass with `preg_replace_callback` that opens allowed container at-rules and strips invalid nested at-rules from their content, collapsing the container entirely if it becomes empty.
* Fix: six IDE diagnostics in `includes/functions/amp-wp-theme-functions.php`. (1) Split `amp_wp_direction()` (void/echo) into `amp_wp_get_direction()` (returns string) + a thin echo wrapper so the concatenation at line 1907 no longer uses a void return value; the existing template caller in `sidebar.php` continues to work unchanged. (2) Replaced the broken `Amp_WP_Public::get_instance()->call_components_method()` call in `amp_wp_do_shortcode()` — `Amp_WP_Public` has no `get_instance()` method — with a direct iteration over the `$amp_wp_registered_components` global, matching the same logic the instance method uses. (3) Fixed possibly-undefined `$area_label` and `$param` in `amp_wp_social_share_get_li()`: the `facebook` switch case only set these variables when `facebook_app_id` was present; when missing, execution fell through to code that used them. Inverted the guard to return early when the app ID is absent. (4) Fixed `amp_wp_comment_reply_link()` assigning the void return of `comment_reply_link()`: set `$args['echo'] = false` so WordPress returns the markup as a string instead of echoing it directly, then echo + return the result. Remaining P1125 is an Intelephense limitation — it cannot narrow `void|string` based on the runtime `echo` key.
* Refactor: WPCS cleanup of `includes/functions/amp-wp-core-functions.php`. Fixed all 36 errors and 4 warnings: added missing `@param` descriptions across 6 functions (`amp_wp_enqueue_style`, `amp_wp_enqueue_script`, `amp_wp_register_component`, `amp_wp_guess_non_amp_url`, `amp_wp_translation_get`, `amp_wp_translation_echo`), corrected swapped `@param` names on `amp_wp_add_inline_style` and `amp_wp_enqueue_inline_style`, fixed `@return bool` on void function, added `@throws` tag, added translators comment, removed structured-array hash notation that tripped `ParamCommentFullStop`, refactored two assignment-in-return statements, added param types for three untyped parameters (`$key`, `$key`, `$parsed_url`), renamed reserved-keyword parameters (`$var` -> `$found`, `$echo` -> `$should_echo`), fixed inline `@var` annotation format, and terminated all param comments with full stops. Removed dead `mysql_get_server_info()` branch in `amp_wp_get_server_database_version()` (the `mysql_*` extension was removed in PHP 7.0; WordPress itself dropped this codepath in 5.x). Fixed `@param null` -> `@param WP_Query|null` on `is_amp_wp()`. No behaviour change.
* Refactor: WPCS cleanup of `includes/functions/amp-wp-formatting-functions.php`. Fixed 1 error + 2 warnings: added missing file docblock, renamed reserved-keyword parameter `$var` to `$tooltip` in `amp_wp_sanitize_tooltip()` and to `$value` in `amp_wp_clean()`. No behaviour change.
* Refactor: WPCS cleanup of `includes/functions/amp-wp-utility-functions.php`. Fixed 4 errors + 3 warnings: renamed reserved-keyword parameter `$string` to `$url` in `amp_wp_remove_query_string()` and added missing description, moved `Copyright:` URL lines to proper `@link` tags so PHPCS no longer treats them as unterminated param comments, added missing `return` statement to `amp_wp_remove_class_action()` (was silently discarding the boolean from `amp_wp_remove_class_filter()`), switched loose `==` to strict `===` in `amp_wp_transpile_text_to_pattern()`, replaced `urlencode()` with `rawurlencode()` in `amp_wp_parse_url()`. No behaviour change.
* Refactor: WPCS cleanup of `includes/functions/amp-wp-ad-functions.php`. Fixed 3 errors: added missing `@param` descriptions on `amp_wp_get_ad_location_data()` and `amp_wp_show_ad_location()`, corrected `@return array` to `@return void` on `amp_wp_show_ad_location()`. No behaviour change.

= 1.7.9 - 2026-05-21 =

* Compat: verified and declared WordPress 7.0 compatibility. Tested up to WordPress 7.0.
* Refactor: PHPCS cleanup of `includes/components/class-amp-wp-img-component.php`. Fixed all PHPDoc issues (missing param descriptions, missing return tags, missing summary periods, incorrect tag ordering), removed assignment-in-condition, fixed closing braces on wrong lines, removed commented-out code, fixed inline comment formatting. Moved the standalone `amp_wp_create_image()` function to `includes/functions/amp-wp-core-functions.php` to resolve `Universal.Files.SeparateFunctionsFromOO.Mixed` (a file must not contain both class and function declarations). No public-API change; function remains available at the same name.
* Refactor: PHPCS cleanup of `includes/components/class-amp-wp-instagram-component.php`. Added separate file and class docblocks (resolves `Squiz.Commenting.FileComment.Missing` and `Squiz.Commenting.ClassComment.SpacingAfter`), fixed PHPDoc tag ordering and added missing param/return descriptions, removed inline `@var` type hint flagged as commented-out code, added missing summary periods. Standardized file docblock tags to `@package`/`@subpackage`/`@author`/`@copyright`/`@since` order.
* Refactor: PHPCS cleanup of `includes/components/class-amp-wp-playbuzz-component.php`. Added separate file and class docblocks, fixed assignment-in-condition, removed inline `@var` type hint, added missing PHPDoc summaries and param/return descriptions, standardized file docblock tag ordering, added `@subpackage` tag. No behaviour change.
* Refactor: cleared all 102 WPCS errors and 40 warnings from `includes/functions/amp-wp-theme-functions.php` (the 2677-line template / globals / properties / queries / social-share / comments helper library). No public-API surface change; file now passes `phpcs --standard=WordPress` with exit 0. PHP syntax check clean. Highlights: replaced two deprecated constants (`STYLESHEETPATH` -> `get_stylesheet_directory()`, `TEMPLATEPATH` -> `get_template_directory()`) inside the template-resolution scan loop in `amp_wp_locate_template()`; backfilled `@param` descriptions on more than thirty docblocks across the file (every helper from `amp_wp_load_template()` and `amp_wp_hw_attr()` through the `amp_wp_set_global` / `amp_wp_get_prop` cluster, `amp_wp_get_branding_info()`, `amp_wp_get_theme_mod()`, `amp_wp_get_option()`, the query helpers, the comment helpers, `amp_wp_min_suffix()`, `amp_wp_collect_post_type_slugs()`, and the social-share trio); flipped six Yoda violations and four loose-comparison `!=` / `==` to strict `!==` / `===` (including the localhost check, the customizer-direction toggle, the menu-walker theme-location compare, the social-share format compare and the taxonomy slug compare); refactored eight assignment-in-condition patterns into explicit fetch-then-test ladders (front-page resolver, branding info, rel-canonical print, locate_template return scan, site-url permalink-prefix block); flagged six `phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames` on the public signatures for `amp_wp_locate_template($require_once)`, `amp_wp_load_template($require_once)`, `amp_wp_body_class($class)`, and four `$default` parameters across the global / prop accessors - renaming would have broken sites that call these by name; tagged the historical `amp-wp-template-default-theme-mod` filter at `amp_wp_get_option()` with a `phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores` plus a justification explaining why renaming to underscores would break any site filtering this 1.0.0-era public hook; added a single `phpcs:ignore` on the `wp_reset_query()` call inside `amp_wp_clear_query()` with a paragraph-long justification about why `wp_reset_postdata()` would not undo a custom `amp_wp_set_query()` swap; replaced `rand()` with `wp_rand()` in `amp_wp_element_unique_id()`; removed the `@` error-silencing on `number_format()` inside `amp_wp_human_number_format()` (cast to float instead, which is the actual fix - non-numeric inputs are already rejected by the early return above); added the missing `/* translators: */` comment on the taxonomy archive title `sprintf( __( '%1$s: %2$s' ) )` in `amp_wp_get_archive_title_fields()` and corrected its textdomain from the placeholder `beetter-amp` to `amp-wp`; collapsed twelve inline comments to proper full-stop terminators; corrected the bogus `@return int` on `amp_wp_comment_link()` (the function only echoes) to `@return void`; tightened the `$_GET` whitelist intersect in `amp_wp_get_canonical_url()` so the existing nonce-recommended ignore lands on the exact `$_GET` token rather than the line above; and scoped a `phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter` on `amp_wp_comment_item()` whose `$comment` / `$args` / `$depth` parameters are required by the `Walker_Comment` callback signature and are read inside the included `comment-item.php` template via scope inheritance. Removed dead code: the long commented-out Google Plus social-share block inside `amp_wp_social_share_fetch_count()` (the service was retired by Google in 2019; the `case 'google_plus':` body had been mid-deletion for years, leaving unreachable `if ( ! is_wp_error( $remote ) )` debris after the preceding `break;`), plus the dangling `'google_plus'` entry in the `amp_wp_social_shares_count()` valid-sites whitelist that was the only remaining live reference to it.
* Refactor: cleared all 8 WPCS errors and 2 warnings from `includes/components/class-amp-wp-carousel-component.php` (the carousel component that renders WordPress galleries and `[amp-wp-slider]` post sliders as `<amp-carousel>`). No behaviour change; file now passes `phpcs --standard=WordPress` with exit 0. Added the missing file-level docblock; gave `transform()`, `handle_gallery()`, `handle_slider()`, `slider_posts()`, and `gallery_attachments()` proper `@param` descriptions; added short descriptions to the two `protected` renderers that had `@param`-only blocks. The `slider_posts()` docblock now carries a multi-paragraph "Uses query_posts() intentionally because..." explanation mirroring the existing `Amp_WP_Public::amp_wp_set_page_query()` docblock that documents the equivalent front-page override: the slider template iterates via `amp_wp_have_posts()` / `amp_wp_the_post()` (in `includes/functions/amp-wp-theme-functions.php`) which fall back to the global `$wp_query` / `$wp_the_query` when no custom query has been staged via `amp_wp_set_query()`, so a fresh `WP_Query` would silently iterate the page's original main query and a `wp_reset_postdata()` paired with it would only rewind the post pointer and leave the slider's query active for the rest of the request. `query_posts()` and `wp_reset_query()` are both `phpcs:ignore`d with short `-- see method docblock` references. Also removed a dead local variable in `config()` (`$array_mohsin`) that built an array identical to the one already being returned literally below it; safe deletion (zero references, return value unchanged).
* Refactor: cleared all 40 WPCS errors and 4 warnings from `includes/components/class-amp-wp-iframe-component.php` (the iframe component that bridges YouTube / Twitter / Facebook / Vimeo / SoundCloud / Instagram onto AMP component tags). No behaviour change; the file now passes `phpcs --standard=WordPress` with exit 0. Highlights: added the missing file-level docblock; backfilled missing `@param` descriptions on 16 methods; renamed the `$string` parameter on `get_iframe_dimension()` to `$html` to drop the reserved-keyword conflict (all four call sites within the same file are positional, no external caller risk); added the missing short description on `$enable_enqueue_scripts`; switched `in_array( $provider, $this->support_sites )` to strict mode; refactored the two assignment-in-condition patterns inside `transform()` into explicit fetch-then-test ladders; gave `get_frame_height()` a typed `@param string $url`; ended the inline `// Fix: remove aparat iframe extra tags` comment with a full stop; and scoped a single `phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase` block around the four `$element->parentNode` / `$element->previousSibling` DOM-API reads in `sanitize()`.
* Fix: `Amp_WP_Html_Util::set_outer_HTML()` now actually replaces the target element with the parsed HTML in-place. The previous implementation appended the parsed fragment to the end of the parent's children, then ran a `while` loop that wiped every child of the parent (including the fragment it had just added and every sibling of the target element). In practice the only caller, the iframe component's SoundCloud rewrite branch (`includes/components/class-amp-wp-iframe-component.php:325`), only landed correct output when the iframe happened to be the sole child of its container; in any other layout, surrounding markup was silently destroyed. New implementation guards on `$element->parentNode`, builds a `DOMDocumentFragment`, parses the HTML under suppressed libxml errors (clearing the queue afterwards so noisy embed markup does not pollute downstream handlers), and uses `replaceChild( $fragment, $element )` so the fragment dissolves into its child nodes at the original element's slot in the parent's child list. Sibling elements are preserved.

= 1.7.8 - 2026-05-14 =

* AMP-Validity: emit the `<amp-auto-ads type="adsense" data-ad-client="ca-pub-XXXX">` body-side element whenever the General Settings "Google Auto Ads" toggle is on and an AdSense publisher ID is resolvable. Closes the validator error "The extension 'amp-auto-ads' was found on this page, but is unused. Please remove this extension." that surfaced when the toggle was enabled alongside Google Site Kit: Site Kit's AdSense AMP integration only fires for the official Automattic AMP plugin (it detects `amp_is_request()` / `AMP__VERSION` / the `amp_post_template_footer` hook), so on AMP WP routes Site Kit never injected the body element that the runtime loader expects to consume. AMP WP now emits the element itself. Publisher ID resolution chain: (1) `amp_wp_adsense_publisher_id` filter for theme/plugin overrides, (2) Site Kit's stored AdSense module setting `googlesitekit_adsense_settings['clientID']` - the same key already inspected by `amp_wp_is_site_kit_adsense_active()`. Returned value is normalised to `ca-pub-XXXXXXXXXXXXXXXX`, accepting any of the three plausible input shapes (`ca-pub-XXXX`, `pub-XXXX`, or the bare numeric ID). When no publisher ID is configured anywhere the element is silently skipped so the page stays validator-clean, and when the toggle is off neither the loader nor the element is emitted. New public API: function `amp_wp_get_adsense_publisher_id()`, function `amp_wp_render_auto_ads_element()`, filter `amp_wp_adsense_publisher_id`. Runtime path: `amp_wp_render_auto_ads_element()` hooks `amp_wp_template_body_start` at priority 5 (runs before `amp_wp_custom_code_body_start` so a manual snippet in the body-start Customizer field continues to layer on top), gates on `amp_wp_is_auto_ads_enabled()`, and uses `printf` with `esc_attr()` on the `data-ad-client` value. No new option storage and no UI field added in this round (publisher ID is auto-resolved from Site Kit, or a filter override): cleanest possible diff that fixes the validator error without scope-creep into an Ads-Manager-style settings UI, which belongs in the `amp-wp-ads-manager` add-on.

= 1.7.7 - 2026-05-14 =

* Feature: native "Google Auto Ads" toggle on the AMP WP > Settings > General page. When enabled, AMP WP enqueues the canonical `amp-auto-ads` runtime loader (`https://cdn.ampproject.org/v0/amp-auto-ads-0.1.js`) on every AMP page via the existing `amp_wp_enqueue_script()` registry, replacing the long-standing workaround of pasting the script into the Customizer's "Codes between <head> and </head> tags" field. Default off; opt-in. Runtime path: `amp_wp_enqueue_auto_ads_script()` is hooked on the existing `amp_wp_template_enqueue_scripts` action and calls `amp_wp_enqueue_script( 'amp-auto-ads', 'https://cdn.ampproject.org/v0/amp-auto-ads-0.1.js' )` - identical pattern to how every other AMP component loader is registered (`amp-form`, `amp-consent`, `amp-geo`, etc.), so the resulting `<script async custom-element="amp-auto-ads" src="..."></script>` tag is rendered by the same `amp_wp_print_scripts()` callback that emits every other component and benefits from automatic dedupe against any sibling caller that registers the same handle. Backward compatibility: when the head-code Customizer field already contains an `amp-auto-ads` loader, the enqueue is skipped (duplicate guard via `amp_wp_head_field_has_auto_ads_loader()`); on save, AMP WP also strips that manually-pasted loader from the head-code field via `amp_wp_auto_ads_strip_manual_loader()` so the native path becomes the single source of truth, surfacing a one-shot dismissible admin notice confirming the cleanup. The settings UI shows two contextual notices: an amber heads-up when the head-code field still contains a loader (preview of what enabling the toggle will clean up) and a blue recommendation when Google Site Kit is detected active with AdSense connected (`amp_wp_is_site_kit_adsense_active()` checks `is_plugin_active( 'google-site-kit/google-site-kit.php' )` plus the AdSense module's stored `clientID` in `googlesitekit_adsense_settings`). New helpers: `amp_wp_is_auto_ads_enabled()`, `amp_wp_is_site_kit_adsense_active()`, `amp_wp_head_field_has_auto_ads_loader()`, `amp_wp_enqueue_auto_ads_script()`, `amp_wp_auto_ads_strip_manual_loader()`. Migration logic hooks both `update_option_amp_wp_general_settings` (subsequent saves) and `add_option_amp_wp_general_settings` (first-ever save where the option does not yet exist - WordPress fires `add_option_<name>` instead of `update_option_<name>` in that branch). New option key `amp_wp_general_settings['google_auto_ads']` (boolean 0/1); save handler always writes a 0/1 explicitly even when the checkbox is unchecked, so the off-transition is recorded in `$new_value` for the migration diff. No public-API breakage; existing manually-pasted loaders in the head-code Customizer field continue to work unchanged when the toggle is left off.
* Fix: amp-geo "country ... not valid, will be ignored" runtime error on every page with the GDPR consent banner enabled. The `ISOCountryGroups.eu` array inside the `<amp-geo>` JSON config was being emitted through `esc_js()`, which is the wrong tool for JSON content: it HTML-encodes `"` as `&quot;`, so amp-geo received a single corrupted string like `PK","AT","BE",...,"CH"` instead of an array of ISO country codes, and silently rejected the whole config (breaking geo-gated consent behaviour). Replaced the `esc_js( implode( '","', ... ) )` pattern at `public/partials/tez/components/gdpr/gdpr.php:22` with `wp_json_encode( array_values( array_map( 'sanitize_text_field', $gdpr_countries ) ) )` so the country list is emitted as a valid JSON array. The `amp_wp_gdpr_country_list` filter contract is unchanged.
* Refactor: WordPress Coding Standards (WPCS) cleanup of the Tez theme directory (`public/partials/tez/`). All 37 template files now pass `phpcs --standard=WordPress` with zero errors and zero warnings, down from 49 errors and 31 warnings across 28 files. Highlights: every inline `//` comment now ends with proper punctuation; all loose-comparison toggle checks like `'1' == $foo_switch` are replaced with `! empty( $foo_switch )` (handles both string `'1'` and integer `1` saved values uniformly without behaviour change); the file-level docblock is present and correctly spaced on every template (added to `header.php`, `footer.php`, `shortcodes/amp-slider.php`, `shortcodes/gallery.php`, and `shortcodes/index.php`; corrected spacing on seven existing docblocks that were flush against the next code line); template-local variables that shadowed WP globals were renamed (`$id` -> `$slider_id` / `$thumbnail_wrapper_id` / `$related_item_id`, `$link` -> `$comment_link`, `$title` -> `$archive_title`); Yoda conditions applied in `components/social-list/social-share.php`; assignments lifted out of `if` conditions in `attachment.php` and `social-share.php`; dead commented-out Google Plus social-share scaffolding (a service Google retired in 2019) removed from `components/social-list/social-links.php`; the slider's `meta_query` for featured-image gating is annotated with `// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query` + justification rather than restructured (no equivalent rewrite exists without changing semantics). All changes are sniff-driven cleanups with no rendering behaviour change.
* Refactor: removed `public/partials/tez/amp-wp-ads-manager/ads.php` and its parent directory from core. The file (a half-completed placement-manager UI sketch from an earlier design - never `require`-d anywhere, hooks two filters that are never fired, calls two helper functions that are not defined) was relocated verbatim to the `amp-wp-ads-manager` add-on at `includes/placement-manager/ads.php` to preserve its design intent for future completion in the right plugin. Reinforces the family architecture rule that core no longer references any add-on slug in any directory path; no public-API change, no add-on consumed the file from this location.

= 1.7.6 - 2026-05-12 =

**Security**

* Comprehensive WordPress Coding Standards security sweep across the entire plugin. Output escaping (`esc_html`, `esc_attr`, `esc_url`, `wp_kses`, `wp_kses_post`) applied to every flagged `echo` / `printf` site in admin partials, settings forms, public templates, customizer controls, and core functions. Superglobal reads (`$_SERVER['REQUEST_URI']`, `$_SERVER['SCRIPT_FILENAME']`, `$_SERVER['PHP_SELF']`, `$_SERVER['HTTP_USER_AGENT']`, `$_SERVER['SERVER_SOFTWARE']`, `$_SERVER['LOCAL_ADDR']`, `$_SERVER['SERVER_ADDR']`) now consistently guarded with `isset()` and run through `esc_url_raw( wp_unslash() )` or `sanitize_text_field( wp_unslash() )` as appropriate per use site. Translation strings with intentional HTML (e.g. `<strong>` for inline emphasis) now route through `wp_kses_post( __() )` instead of bare `_e()` so the markup is preserved while bot-injected HTML is filtered out. Legacy `// WPCS: XSS ok.` comments replaced with modern `// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped --` annotations with a one-line justification each. Net effect: the `WordPress.Security.EscapeOutput`, `WordPress.Security.NonceVerification`, and `WordPress.Security.ValidatedSanitizedInput` sniffs report 0 errors and 0 warnings across the plugin (down from 311 errors before the sweep). Total WPCS reduction: 1,448 -> 632 (56%). No behavioural change - all edits are escaping/sanitizing additions or `phpcs:ignore` annotations on canonical safe-output patterns.

**Added**

* New "Premium Extensions" tab on the AMP WP Settings page (`admin.php?page=amp-wp-settings#settings-premium-extensions`). Central admin home for AMP WP family add-ons that need configurable settings without forking a full settings tab. Add-ons populate the tab via the new `amp_wp_premium_extensions_section` action - callbacks echo a card (heading + `form-table` row group) inside the tab body, and the existing `amp_wp_save_setting_sections` action persists each add-on's option independently. New files: `includes/admin/settings/class-amp-wp-premium-extensions.php` (registers the tab) and `admin/partials/settings/amp-wp-admin-premium-extensions.php` (the form scaffold).
* Premium-extension registration API: `amp_wp_register_premium_extension( string $slug, array $args = [] ): void`, `amp_wp_get_registered_premium_extensions(): array<string,array<string,string>>`, and `amp_wp_has_active_premium_extension(): bool` (all in `includes/functions/amp-wp-core-functions.php`). Each AMP WP family add-on self-registers once on bootstrap with a slug + display metadata (`name`, `description`, `version`, `url`). Idempotent: registering the same slug twice is a no-op. New companion filter `amp_wp_premium_extensions` for advanced reordering, suppression, or unit-test snapshot/restore. The empty-state partial reads the registry to render the active-extensions list (instead of hard-coding "AMP WP - Contact Form 7").
* Template-path registration: new public functions `amp_wp_register_template_path( string $absolute_path ): void` and `amp_wp_get_registered_template_paths(): array` (in `includes/functions/amp-wp-theme-functions.php`) let AMP WP family add-ons ship their own AMP templates instead of having them live inside core's "Tez" theme directory. Resolution order is `<wp-theme>/amp-wp/<template>` (site override - unchanged) -> registered add-on paths in registration order -> core's Tez directory as the catch-all fallback. End-user override convention preserved verbatim. New filter `amp_wp_template_paths` lets advanced users reorder or suppress paths per-request. Companion change in `amp-wp-woocommerce` migrates eight WC-specific AMP partials out of core to restore the family architecture rule "Core never references any add-on by name".
* New extension hook `amp_wp_layout_setting_after_show_comments` fires inside the Layout Settings -> Single Post Page table, immediately after the "Show Comments" `<tr>`. Designed for AMP WP family add-ons (e.g. AMP WP - Comments uses it to print a "Show Comment Replies" toggle row) to extend the comment-related settings block without forking `admin/partials/settings/amp-wp-admin-layout.php`. Callbacks must echo a complete `<tr>...</tr>` matching the surrounding form-table markup; inputs may piggyback on the existing `amp_wp_layout_settings[...]` name prefix to persist via the existing save handler at `Amp_WP_Layout::amp_wp_save_layout_settings()` - no separate option, no separate save flow.

**Changed**

* The "Premium Extensions" tab is now hidden by default - it only registers when at least one AMP WP family add-on (CF7, Comments, WooCommerce, WPForms, Gravity Forms, Ads Manager) has called the new `amp_wp_register_premium_extension()` function on its bootstrap. Previously the tab appeared on every install, even on a fresh one with no add-ons active, where it rendered an empty-state hint. Back-compat fallback: if an older add-on hooks `amp_wp_premium_extensions_section` directly without registering, the action presence still triggers the tab. Both the tab definition and the partial loader gate on the same check, so a stale anchor link to `#settings-premium-extensions` no longer paints orphaned form markup either.

**Fixed**

* Browsers fetching `/favicon.ico` (and any other root-level static-file URL) on mobile no longer trigger a 404. `Amp_WP_Public::amp_wp_auto_redirect_to_amp()` fed `$_SERVER['REQUEST_URI']` through `Amp_WP_Content_Sanitizer::transform_to_amp_url()`, which blindly appended `/amp` to any internal URL it didn't already recognise as an AMP path or wp-content asset. When a site has no static `favicon.ico` file at the document root, the browser-issued favicon request fell through to `index.php`, hit `template_redirect` on mobile, and the auto-redirect bounced the browser to `/favicon.ico/amp` - which never matches a rewrite rule and 404s. Same shape would fire for any root-level static file (`sitemap.xml`, `robots.txt`, `ads.txt`, `manifest.json`, etc.) when no physical file exists. Fix: `transform_to_amp_url()` now short-circuits and returns the URL unchanged when the path's basename has a static-file extension (35-entry list covering icons, images, scripts, styles, fonts, archives, media, metadata). List is filterable via the new `amp_wp_non_amp_url_extensions` filter so unusual extensions can be added per-site. Case-insensitive match. Side benefit: every other call site of `transform_to_amp_url()` - filters on `term_link`, `author_link`, `attachment_link`, `post_type_link`, the nav-menu `href` filter, the canonical-URL helper - is now also protected from inadvertently rewriting static-file URLs that themes or third-party plugins happen to pass through them.
* Search-engine crawlers no longer get caught by the mobile auto-redirect to AMP. `Amp_WP_Public::amp_wp_auto_redirect_to_amp()` previously checked only `wp_is_mobile()`, which returns true for Googlebot-Mobile, Bingbot, YandexBot, DuckDuckBot, AppleBot, FacebookExternalHit, Twitterbot, LinkedInBot, Slurp, AhrefsBot, and SemrushBot. Each was being 302-redirected from `https://yoursite.com/` to `https://yoursite.com/amp/`; the AMP page's `<link rel="canonical">` then pointed back at the original, and Google Search Console flagged the canonical URL as "Redirect error" in Page Indexing because the URL it was trying to index kept redirecting. Google's AMP guidelines explicitly require the canonical to be served directly to bots; AMP discovery happens via the head-tag `<link rel="amphtml">`, not a UA redirect. Fix: new public helper `amp_wp_is_search_engine_bot(): bool` in `includes/functions/amp-wp-core-functions.php` checks `HTTP_USER_AGENT` against a 12-entry list (substring match, case-insensitive); the redirect path bails early when it returns true. The cache-plugin desktop fallback (which injects the UA-sniffing JS redirect) is implicitly safe because the server-side branch already short-circuited for bots. Filterable via `amp_wp_search_engine_bot_user_agents` (UA fragment list) and `amp_wp_is_search_engine_bot` (final boolean). Unit-tested against 10 real-world UA strings (7 bots + 3 real users) - 10/10 pass. After the fix ships, click "Validate fix" inside Search Console; Google re-crawls the affected URLs in 1-7 days and the "Redirect error" count drops to 0.
* Pagination on AMP archives was broken for any URL of the form `/<archive>/amp/page/N/`. The URL-based branch of `is_amp_wp()` (used during `pre_get_posts` before `template_redirect` fires, when the `amp` query var is not yet set on `$wp_query`) only matched the AMP token at the very start or very end of the request URI - so `/shop/amp/page/2/`, `/product-category/<slug>/amp/page/2/`, `/category/<slug>/amp/page/2/`, `/tag/<slug>/amp/page/2/`, and `/<page>/amp/comment-page-N/` all returned `false`. The cascade was severe: any third-party `pre_get_posts` callback gated on `is_amp_wp()` (notably the AMP WP - WooCommerce add-on's priority-0 bridge that re-registers `WC_Query::pre_get_posts` at priority 101) bailed early, AMP WP's query isolation between priorities 1 and 100 wiped WooCommerce's product-archive setup, and `/shop/amp/page/2/` rendered the shop *page* (post type `page`, ID 588) with `paged=2` instead of the products grid. The old second regex also had a separate false-positive bug where `amp/*` allowed zero trailing characters and matched URIs like `/ampersand/` and `/amp-tree/`. Both regexes replaced with a single boundary-anchored pattern `#(?:^|/)<amp>(?:/|$)#` that detects AMP as a whole path segment anywhere in the URI; verified against 20 URL patterns (20/20 pass) including paginated archives, sub-directory installs, and the previously-broken false positives. Live verification on `/shop/amp/page/2/`: `post_count=8 found_posts=50 max_num_pages=7 paged=2 post_type=product is_archive=1 is_post_type_archive=1 is_amp_wp=1 is_shop=1 is_woocommerce=1` (was `post_count=1 paged=2 post_type="" is_archive=0 is_page=1 is_amp_wp=0` before the fix).
* The "Premium Extensions" tab was rendering an empty `<i>` tag with no visible icon next to the label. The original `amp-wp-admin-icon-star` class did not exist in `admin/css/amp-wp-admin.css` or in the icon font at `admin/fonts/amp-wp-admin-icons/`. Switched to `amp-wp-admin-icon-feather`, which is defined and not already in use by another tab (every star / puzzle / rocket / crown glyph variant is missing or taken). CSS and font are unchanged.

**Docs**

* Inline help text added to five settings tabs: Structured Data, GDPR Compliance, Notice Bar, Layout Settings, and General Settings. Around 30 new `<p class="description">` blocks under every previously-undocumented option so admins do not need to consult external docs to know what each toggle / select / textarea does. Highlights: the GDPR master toggle now explains the EU/EEA IP gate and the third-party-script suppression behaviour; Layout's "Show Slider on Home Page", "Show Share Box In Posts", and "Show Related Posts" parent toggles each get a description plus descriptions on every sub-control; General's "Enable AMP on Home Page", "Enable AMP on Search Page", "Disable AMP on Post Types", and "Disable AMP on Taxonomies" finally have inline guidance. All strings wrapped in `esc_html_e( ..., 'amp-wp' )` using the same `<p class="description">` pattern already established by the GDPR partial. No PHP / behaviour changes - pure documentation in the admin UI.
* Inline help text added under the OneSignal options on the 3rd Party Plugins tab (`admin.php?page=amp-wp-settings#settings-third-party-plugins-support`). Three new `<p class="description">` blocks: under "App ID" (UUID format and where to find it - OneSignal dashboard -> Settings -> Keys & IDs), under "Position" (subscription bell placement and when to override the default), and under "Subdomain" in the HTTP Site row (OneSignal-hosted HTTPS subdomain like `yoursite.os.tc` that non-HTTPS sites need for the push subscription frame; HTTPS sites should leave the field blank).

= 1.7.5 - 2026-05-07 =
* Fixed: Mobile browsers no longer get stuck in a "too many redirects" loop on AMP URLs after an admin changes "Exclude AMP by URL" or "Exclude URLs From Auto Converting". Plugin-issued redirects (AMP-router and mobile auto-redirect) now downgrade the AMP-excluded redirect from `301 Moved Permanently` to `302 Found` and emit `nocache_headers()`, so neither browsers, proxies, nor CDNs can pin a stale redirect direction once the underlying setting flips. The desktop-side fallback that injects a JavaScript redirect to the AMP URL when a caching plugin is detected now also defines `DONOTCACHEPAGE` and emits no-cache headers, preventing cache plugins that do not separate cache by device from serving a desktop response (which contains the mobile-UA-sniffing redirect script) to mobile visitors and triggering an infinite loop in the mobile browser. Existing affected clients are auto-recovered on their next request to a now-AMP-excluded URL: the AMP-router redirect response carries a one-shot `Clear-Site-Data: "cache"` header (HTTPS-only, gated by an `amp_wp_cache_cleared` cookie marker so normal HTTP caching is preserved on subsequent redirects), which evicts any stale `301` pinned in the mobile browser before it follows the redirect.
* Fixed: `amp-state` elements are no longer stripped from AMP pages when the AMP WP Comments plugin is active. The content sanitizer's allowed-tag list (`tags-list.php`) did not include `amp-state`, causing the element to be silently removed during the HTML clean-up pass, which broke the AMP threading state used by the comment reply form.
* Fixed: Per-comment Reply links now appear in threaded comment lists on AMP pages. `amp_wp_comment_item()` was not forwarding the `$depth` argument from `wp_list_comments()` to the `comment-item.php` template. WordPress 6.8 updated `get_comment_reply_link()` to return null when depth is 0, so every reply link resolved to nothing. The function now accepts and passes `$depth` (defaulting to 1).
* Fixed: Per-comment Reply button now shows the label "Reply" instead of the comment form title "Leave a Reply". The template was pulling from `amp_wp_translation_get('comments_reply')`, which returns the form heading. The button now uses `__('Reply', 'amp-wp')` directly.
* Fixed: AFS Analytics configuration JSON was malformed. Missing commas after `websiteid` and `title` properties caused a JSON parse error in the AMP runtime, silently disabling analytics on AMP pages when AFS Analytics was configured.
* Improved: `includes/class-amp-wp-redirect-router.php` brought into WordPress Coding Standards compliance. Added a proper file-level docblock (the previous one only documented the class). Sanitized `$_SERVER['REQUEST_URI']` via `isset()` + `esc_url_raw( wp_unslash() )` in `redirect_to_amp_url()`. Renamed the singleton accessor `Run()` to `run()` to satisfy WPCS snake_case naming; the class is documented as not-public in the family's public API contract, so this rename is internal and the single caller in `Amp_WP_Public::define_public_hooks()` was updated. None of the six add-ons (cf7, comments, woocommerce, wpforms, gf, ads-manager) reference the class.
* Improved: `public/class-amp-wp-public.php` brought into WordPress Coding Standards compliance. Sanitized and unslashed all `$_SERVER['REQUEST_URI']` and `$_SERVER['HTTP_HOST']` reads via `esc_url_raw( wp_unslash() )` / `sanitize_text_field( wp_unslash() )` with `isset()` guards in `redirect_to_start_point_amp()`, `redirect_to_end_point_amp()`, and `amp_wp_get_requested_page_url()`. Switched all internal same-origin redirects from `wp_redirect()` to `wp_safe_redirect()`. Replaced loose `==` comparisons with strict `===` in `amp_wp_transform_post_link_to_amp()`, `amp_wp_init_json_ld()`, and `is_amp_excluded_by_url()`; added `true` strict flag to all `in_array()` calls in `amp_version_exists()`. Refactored the empty-body if/elseif assignment-in-condition chain in `amp_wp_template_loader()` into an explicit early-return ladder that preserves the "first matching template wins" semantics including the static-home-page query override and the attachment-template `prepend_attachment` filter removal. Renamed the private helper `_amp_wp_can_call_component_method()` to drop the leading underscore (visibility is already encoded in the `private` keyword) and removed its unused `&$args` reference parameter; the single internal caller was updated to match. Renamed the `$default` parameter on `amp_wp_get_option()` to `$default_value` to avoid the PHP reserved-keyword shadow. Filled in 19 missing or incomplete `@param` doc-comment lines across 13 methods. Removed three blocks of dead commented-out code that the surrounding live code superseded. No public-API behaviour change: every method signature exposed to add-ons is preserved.
* Added: `tools/test-redirects.sh` - a curl-based smoke harness that asserts every plugin-issued redirect on a target site behaves the way the no-cache + Clear-Site-Data + 302 fix promises. Run it against staging or production before pushing a release: `BASE_URL=https://your-site.com ./tools/test-redirects.sh`. Covers eight scenarios (fresh AMP-excluded redirect, cookie-gated re-request, desktop UA, full chain termination, AMP-included single post, mobile auto-redirect cache headers, query-string preservation, paginated AMP URL) with 29 individual assertions including 302 status, `Cache-Control: no-cache` + `no-store`, `Clear-Site-Data: "cache"` presence/absence per cookie state, hardened `Set-Cookie` flags (Secure / HttpOnly / SameSite=Lax), Location target, and AMP markers in served bodies. Configurable per site via env vars (BASE_URL, AMP_EXCLUDED_URL, AMP_INCLUDED_URL, NON_AMP_URL, PAGINATED_AMP_URL). Exits 0 on green, 1 on any failure with a list of failed assertions.

= 1.7.4 - 2026-05-01 =
* Fixed: Header and sidebar logo image no longer disappears when a logo is set in AMP WP branding settings. The v1.7.2 PHPCS escaping pass wrapped the logo output in `wp_kses_post()`, which strips non-standard HTML elements including `<amp-img>` and `<amp-anim>`. The logo tag was silently removed on every page load, leaving an empty link in the header. A dedicated `amp_wp_kses_amp_img()` helper now sanitizes logo output using `wp_kses()` with `amp-img` and `amp-anim` added to the allowed-tag list, preserving `src`, `width`, `height`, `class`, `alt`, `sizes`, `layout`, and `on` attributes.
* Improved: AMP WP meta box in the post/page editor sidebar rebuilt with a professional toggle switch UI. The meta box is now pinned to the top of the sidebar panel (priority high), the reorder controls are hidden, the panel heading inherits WordPress default weight to match Categories and Tags, and the Disable AMP control uses a pure-CSS toggle with a red active state and an AMP WP Settings shortcut link in the footer.
* Fixed: Notice Bar and GDPR Compliance no longer silently fail to appear on AMP pages. Both save functions stored the enabled switch as integer `1` while the frontend callbacks checked for string `'1'` with strict `===` comparison, causing the condition to always evaluate false and neither component to render regardless of the saved setting.
* Fixed: Notice Bar settings form was missing nonce verification in the save handler, allowing settings to be written without a valid nonce. The form nonce field is now aligned with the shared `amp_wp_settings_nonce` token used across all settings tabs, and the save function validates it before processing POST data.
* Fixed: Notice Bar accept button text field incorrectly fell back to an empty `array()` instead of an empty string when no value was saved, causing a type mismatch when the value was passed to `esc_attr()`.
* Fixed: GDPR `wp_dropdown_pages()` arguments `show_option_no_change` and `option_none_value` were explicitly set to `null`, overriding WordPress defaults and generating PHP 8 type deprecation warnings. Both are now set to empty strings.
* Fixed: GDPR consent form accept and reject actions no longer silently fail to redirect after submission. Both `AMP-Access-Control-Allow-Source-Origin` and `AMP-Redirect-To` response headers had a trailing space appended to their values, causing header parsing failures in the AMP runtime.
* Fixed: "Privacy Settings" re-open button label in the GDPR post-consent UI was a hardcoded English string. It is now wrapped with `__()` and translatable.

= 1.7.3 - 2026-04-30 =
* Fixed: Yoast SEO integration now works correctly on all modern Yoast SEO versions (v14 and above). The previous guard checked for WPSEO_OpenGraph, a class removed in Yoast v14 (2020), causing meta description, Open Graph, and Twitter Card tags to be silently omitted from every AMP page on any current Yoast install. The guard now detects both modern (YoastSEO()) and legacy (WPSEO_OpenGraph) APIs.
* Fixed: Meta description, Open Graph, and Twitter Card tags are now output on AMP pages for Yoast SEO v14+ using the wpseo_head presenter pipeline, replacing the deprecated WPSEO_Frontend, WPSEO_Twitter, and wpseo_opengraph APIs which were removed in Yoast v14.
* Fixed: AMP pages no longer output a duplicate title or robots meta tag when Yoast SEO is active. The new presenter filter strips Title, Robots, Canonical, Rel-Prev, and Rel-Next presenters since AMP WP manages those independently.
* Fixed: Homepage title sync with Yoast SEO now works on AMP front pages with Yoast v14+ using YoastSEO()->meta->for_post(). Previously the function fell through silently on modern Yoast because the WPSEO_Frontend::get_instance() guard evaluated to false.
* Fixed: AMP front page no longer times out when Yoast SEO is active. The new homepage title code called Yoast's meta API, which internally triggers wp_get_document_title() and re-fires the pre_get_document_title filter, causing the callback to re-enter itself indefinitely. This produced a 300-second PHP timeout visible only on the AMP front page. A re-entrancy guard now prevents the loop.
* Fixed: All In One SEO Pack (AIOSEO) integration now works with AIOSEO v4 and above. The previous code called All_in_One_SEO_Pack() directly, a class removed in AIOSEO v4 (2021), so meta description, Open Graph, and Twitter Card tags were silently missing from AMP pages on all modern AIOSEO installs. The guard now detects AIOSEO v4+ via the aioseo() function and calls aioseo()->head->output() to render the full meta block, while retaining the legacy v3 code path as a fallback.
* Fixed: AIOSEO homepage title sync now works on v4+ using aioseo()->meta->title->getPostTitle(). The previous call used a non-existent get() method and silently returned no title on all modern AIOSEO versions.
* Fixed: WP-Optimize compatibility fully rewritten. The previous fix attempted to remove a WP-Optimize hook at plugin-load time, but AMP WP loads alphabetically before WP-Optimize so the class did not exist yet and the removal never executed. Three specific issues are now resolved: (1) HTML compression: WP-Optimize's output buffer ob_start has no AMP check and was compressing AMP HTML; it is now removed before it fires. (2) Delay JS: the Delay JS feature uses a regex that matches all script tags including AMP runtime and component scripts, rewriting src to data-src and preventing them from loading; it is now disabled on AMP pages. (3) Page caching: AMP responses are now excluded from WP-Optimize's page cache via the wpo_can_cache_page filter so corrupted output is never stored. CSS and JS minification were already safe via WP-Optimize's own is_amp_endpoint() check.
* Fixed: WP Fastest Cache compatibility extended to cover all features that have no built-in AMP check. Previously only JS combine/minify, CSS combine, lazy load, and Google Fonts were disabled on AMP pages. HTML minification (standard and aggressive), CSS minification, inline CSS minification, and HTML comment removal are now also disabled; all five ran unconditionally on AMP pages in every prior version.
* Fixed: OneSignal Web Push Notifications integration corrected on four fronts. (1) The amp-web-push component was never injected into AMP pages because the add_action call was wrapped in an inverted function_exists guard that always evaluated false after the function was defined directly above it. (2) The HTTP site toggle was saved under the key onesignal_http_site_switch but read back as onesignal_switch_http_site, so HTTP mode was never activated. (3) HTTPS helper and permission dialog HTML pointed to outdated bundled copies inside the AMP WP plugin instead of the installed OneSignal plugin's own sdk_files, which load the current SDK from cdn.onesignal.com. (4) A missing isset on onesignal_app_id generated a PHP notice when the App ID field was left empty.
* Fixed: Structured data schema type (`schema_type_for_post`) is now validated against the allowed set (`Article`, `NewsArticle`, `BlogPosting`) before being passed to the JSON-LD generator. A tampered or corrupted database value could previously write an arbitrary `@type` string into the JSON-LD output, producing invalid structured data that fails Google's Rich Results validator.
* Fixed: `datePublished` and `dateModified` in JSON-LD structured data now use ISO 8601 datetime format with timezone offset (`DATE_ATOM`, e.g. `2024-01-15T10:30:00+05:00`) instead of date-only format (`Y-m-d`). The previous date-only format omits time and timezone information, which is non-compliant with the Schema.org `DateTime` specification and reduces precision for Google's structured data parser.
* Fixed: `interactionStatistic.userInteractionCount` in JSON-LD structured data is now cast to integer before output. `get_comments_number()` returns a string; Schema.org defines `userInteractionCount` as a `Number` type, so a string value caused Google's Rich Results validator to report a type mismatch on every post.
* Fixed: `Article`, `NewsArticle`, and `BlogPosting` schemas now always include an `image` field. When a post has no featured image, the generator falls back to the organization logo (when a logo is configured in AMP WP branding settings). Google treats `image` as required for all Article-type rich results: posts without a featured image previously emitted no `image` at all, silently disqualifying them from rich result eligibility in Google Search.
* Fixed: Yoast SEO homepage JSON-LD sync now actually fires on AMP pages. The `amp_wp_json_ld_website_` filter (note the trailing underscore, which is the real filter name emitted by the generator) was registered without the trailing underscore as `amp_wp_json_ld_website`, causing the callback to be permanently silently skipped. The site name and alternate name set in Yoast SEO options were never merged into the WebSite schema on AMP homepages.
* Removed: Multi Rating compatibility. The plugin was closed on WordPress.org and has not been updated since 2018; it is no longer installable. The compatibility block was also registered unconditionally with no detection guard, meaning three dead function calls fired on every AMP page load even when Multi Rating was not installed. Removed the `add_action` call, the `multi_rating()` method, and the file-header reference.
* Verified: Snip Rich Snippets (`rich-snippets-schema`) compatibility with AMP pages confirmed correct and complete. AMP WP's template system does not fire `wp_head`, so Snip's own head hook is silent on AMP pages. The compatibility layer re-routes Snip's `print_snippets` output to `amp_wp_template_head` or `amp_wp_template_footer` (respecting Snip's own "snippets in footer" setting) exactly once per request. Snip's JSON-LD output is AMP-spec-valid (`<script type="application/ld+json">` is permitted in AMP pages) and coexists with AMP WP's own structured data blocks without duplication.
* Fixed: wpForo Forum compatibility now works correctly with wpForo 3.x. The previous detection used `class_exists('wpForo')`, which always returned false on wpForo 3.x because that version moved to a PHP namespace (`wpforo\wpforo`). As a result, the `amp_wp_amp_version_exists` filter was never registered, and AMP WP silently served AMP pages on wpForo forum URLs on all modern wpForo installs. Detection now uses `defined('WPFORO_VERSION')`, which is reliably set by all wpForo versions.

= 1.7.2 - 2026-04-26 =
* Fixed: Google Analytics AMP tag now correctly detects GA4 (`G-XXXXXXXX`) vs Universal Analytics (`UA-XXXXXXX-X`) IDs. GA4 IDs now output `<amp-analytics type="gtag">` with `gtag_id` / `config` vars, while UA IDs continue to use the legacy `type="googleanalytics"` vendor. This resolves the persistent `[AmpAnalytics googleanalytics] Warning: [object Object]` console error that appeared after migrating to a GA4 Measurement ID.
* Fixed: JSON-LD structured data: 11 Schema.org / Google Rich Results compliance issues resolved: `mainEntityOfPage` is now a typed `WebPage` object instead of a plain URL; `publisher` outputs an inline `Organization` when no logo is configured so the `#organization` reference never dangles; `headline` capped at 110 characters per Google AMP article spec; all `@context` values normalised to `https://schema.org` (no trailing slash); all `@id` values upgraded to absolute URLs; `WebSite` schema uses `description` instead of `alternateName` for the site tagline; `AudioObject` now includes the required `name` property; `post_excerpt` is sanitised before output; `wordCount` restricted to Article sub-types only; `WebPage potentialAction` corrected to a direct `SearchAction` object; `JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE` added to JSON output flags.
* Fixed: AdSense Auto Ads (and any custom `<head>` code) now outputs via the `amp_wp_template_head_deferred` hook at priority 5 instead of `amp_wp_template_head`, ensuring the snippet is injected after AMP boilerplate styles are written, which is required for `<amp-auto-ads>` to be recognised and fired correctly by the AMP runtime.
* Improved: Upgraded Font Awesome from 4.7.0 (MaxCDN) to 6.7.2 (cdnjs), eliminating browser console warnings about incorrect glyph bounding boxes present in the FA 4.7.0 font file. The v4-shims stylesheet is loaded automatically so all existing `fa fa-*` icon classes continue to work with no template changes required.
* Improved: WordPress Coding Standards (PHPCS) compliance in `amp-wp-template-functions.php`: all output values are now properly escaped (`esc_attr()`, `wp_kses_post()`), loose `==` comparisons replaced with strict `===`, variable assignments moved out of conditions, `in_array()` calls updated with strict mode, reserved-keyword parameter names renamed, and PHPDoc parameter annotations corrected throughout.
* Improved: WordPress Coding Standards (PHPCS) compliance in `class-amp-wp-html-util.php`: added file-level docblock, renamed camelCase local variables to snake_case in `renameElement()`, fixed variable assignments in conditions in `get_child_tag_attribute()`, corrected a `@param` name mismatch in the `child()` method docblock, and added `phpcs:ignore` annotations for PHP DOM API built-in properties (`parentNode`, `childNodes`, `ownerDocument`, etc.) that cannot be renamed.

= 1.7.1 - 2026-04-26 =
* Fixed: Select2 CSS in the admin now scoped to `.select2-container.amp-wp-select-container`, preventing style conflicts when ACF (Advanced Custom Fields) or other plugins ship their own Select2 instance on the same admin page.
* Fixed: Comments section on single posts no longer hides the "Add Comment" button when a post has zero comments; comments are open so visitors can start a discussion. The comment list itself is still only rendered when comments exist.
* Improved: Add-ons tab card layout: Install/Update action button now appears above the version info line, and the action area uses flexbox for consistent alignment across varying content lengths.
* Fixed: PHP Warning "Undefined array key amp_wp_settings_nonce_field" in settings save handler. The `||` operator caused the nonce value to be read before its existence was confirmed; corrected to `&&`.
* Fixed: WP Rocket redirect loop on URLs excluded from AMP. WP Rocket was serving a stale cached redirect (non-AMP -> AMP) after the URL was added to the exclusion list, while the plugin redirected back (AMP -> non-AMP). Excluded URLs are now pushed into WP Rocket's `rocket_cache_exclude_uris` bypass list so cached redirects are never served for them, and `DONOTCACHEPAGE` is set on the current request to prevent a new cache file from being written.
* Improved: `class-amp-wp-plugin-compatibility.php` file and class docblocks rewritten to professional WordPress PHPDoc standard. The file header now documents all supported third-party plugins grouped by category (Cache & Performance, SEO, Multilingual, Media & Lazy Load, Social & Comments, Forms, Structured Data, Links & Permalinks, Miscellaneous) with plugin URLs; class docblock describes the conditional-detection architecture.
* Removed: WP Speed Grades Lite compatibility. The plugin was closed on WordPress.org on 1 March 2021 for guideline violation and is no longer available for download. Removed the `WP_SPEED_GRADES_VERSION` detection block, the `pre_init()` method, and all references in docblocks.
* Fixed: Translated AMP pages returning 404 when Polylang is active with subdirectory language URLs (e.g. `/uk/category/post/amp/`). The AMP URL transformer was reading the language prefix segment (e.g. `uk`) as a post slug and failing to detect the AMP endpoint. Language slugs are now added to the URL transformer exclusion list when Polylang is configured in subdirectory mode, mirroring the existing WPML fix.

= 1.7.0 - 2026-04-23 =
* Added: Add-ons tab now shows installed and latest version for each premium add-on (e.g. "Installed: v2.0.0 | Latest: v2.0.0").
* Added: Download button on the Add-ons tab hides when the add-on is already installed and up to date.
* Added: Update button appears on the Add-ons card when an installed add-on is outdated (installed version < latest version).
* Added: Latest version is fetched from the public `amp-wp-addon-versions` GitHub repository and cached for 12 hours via WordPress transients.
* Added: Manual version cache refresh: visiting the Add-ons page with `?amp_refresh_versions=1` clears the cached version and forces a fresh fetch.
* Added: Full Help & Documentation page: replaces the placeholder with Getting Started, Content Control, Design, Analytics & Ads, Embeds, Add-Ons, and FAQ sections.
* Fixed: Undefined variable `$page` warning in `amp-wp-admin-header.php`. The active tab highlight now reads the current page from the URL query parameter directly.
* Fixed: `wp_redirect()` replaced with `wp_safe_redirect()` + `exit` on activation redirect to prevent open redirect and ensure execution stops.
* Fixed: `amp_wp_version_check_using_wpapi()` wrapped in `function_exists` guard and moved to utility functions to prevent fatal errors on duplicate inclusion.

= 1.6.0 - 2026-04-14 =
* Added: Instagram Reels (`/reel/`) and IGTV (`/tv/`) embed support alongside existing `/p/` posts via `amp-instagram`.
* Added: `data-captioned` passthrough: preserves Instagram caption flag when the original embed included `data-instgrm-captioned`.
* Added: Auto layout detection for `amp-iframe`: sets `layout="responsive"` when both width and height are present; `layout="fixed-height"` when only height is available.
* Added: Pass-through of `layout`, `resizable`, `scrolling`, and `srcdoc` attributes to `amp-iframe` for fuller AMP spec coverage.
* Improved: `amp-facebook` output now includes a proper closing tag and corrected attribute spacing.
* Improved: `amp-playbuzz` component now supports both `src` (full URL) and `data-item` (item ID) conventions, plus optional `data-item-info`, `data-share-buttons`, and `data-comments` attributes.
* Improved: `amp-instagram` dimensions updated to `1x1` with `layout="responsive"` per AMP documentation.
* Improved: JSON-LD `@context` updated from `http://schema.org` to `https://schema.org`.
* Improved: `SearchAction` structured data updated to use the `EntryPoint` object format with `urlTemplate` and `search_term_string` per current Schema.org specification.
* Improved: Tumblr share button updated to the current widget API endpoint (`https://www.tumblr.com/widgets/share/tool`) with correct parameters.
* Improved: `aria-label` attribute on social share buttons corrected (was producing malformed HTML).
* Fixed: PHP 8 compatibility, replaced `@fopen()` error suppression in `Fastimage.php` with a proper `set_error_handler` / `restore_error_handler` pattern.
* Fixed: PHP 8 compatibility, replaced `@getimagesize()` suppression with an explicit `false !== $size` check.
* Fixed: Replaced all deprecated `join()` calls with `implode()` across the codebase.
* Removed: `amp-addthis` from the supported AMP components list (component retired by the AMP Project).
* Removed: `amp-gfycat` from the supported AMP components list (component retired by the AMP Project).

= 1.5.19 - 2026-04-03 =
* Fixed: Resolved PHP 8.4 compatibility issue: deprecated return type warning in `Amp_WP_Html_Util::loadHTML()` due to incompatible method signature with `DOMDocument::loadHTML()`. Added `#[\ReturnTypeWillChange]` attribute, explicit `: bool` return type, and proper return value.
* Removed: Dropped Vine embed support as the platform was permanently shut down in 2017.

= 1.5.18 - 2026-01-28 =
* Fixed: PHP 8.2 compatibility: "Trying to access array offset on value of type bool" warning in `amp-wp-theme-functions.php` when sharing posts without featured images on Pinterest.
* Fixed: Translation loading notice, text domain now loads at the `init` action hook, complying with WordPress 6.7.0+ requirements.
* Fixed: Undefined variable warning in layout settings by removing unused template code.

= 1.5.17 - 2024-10-01 =
* Fixed: Missing file documentation in `Amp_WP_Redirect_Router` class to adhere to WordPress Coding Standards.
* Improved: Inline documentation for class methods, function parameters, and sanitization functions.
* Fixed: `explode()` called with potentially null parameters, now validates before calling.
* Fixed: `DOMDocument::__construct()` called with null `$version` parameter.

= 1.5.16 - 2024-06-03 =
* Fixed: Resolved XSS vulnerabilities.
* Fixed: Corrected string concatenation to comply with coding standards.

= 1.5.15 - 2023-07-09 =
* Enhancement: Updated AMP tags list.
* Note: Overall improvements.

= 1.5.14 - 2021-12-24 =
* Fixed: Social sharing issue on single post.

= 1.5.13 - 2021-08-19 =
* Note: PHP 8 compatibility improvements.
* Fixed: Undefined variables.

= 1.5.12 - 2021-06-23 =
* Added: SG Optimizer plugin compatibility.
* Improved: JSON-LD schema revision.
* Fixed: Yoast SEO v14 compatibility.
* Fixed: Color issues.
* Fixed: AMP Carousel component updated to v0.2.
* Fixed: RTL CSS fixes.

= 1.5.11 - 2019-11-15 =
* Feature: Description display on archive pages.
* Feature: OneSignal Push Notifications support.
* Note: Added Facebook App ID option for post sharing.

= 1.5.10 - 2019-09-17 =
* Fixed: Resolved self-canonical AMP URL issue when AMP URL format is set to "End".

= 1.5.9 - 2019-09-16 =
* Fixed: Resolved self-canonical AMP URL issue when AMP URL format is set to "Start".

= 1.5.8 - 2019-07-18 =
* Feature: Option to exclude any taxonomy or post type from AMP under General settings.
* Fixed: `amp-mustache` script enqueue error when using Contact Form 7.

= 1.5.7 - 2019-04-30 =
* Fixed: Broken editor issue when editing a post or page.

= 1.5.6 - 2019-04-29 =
* Added: AMP WP - Contact Form 7 Extension.
* Fixed: Social sharing link.

= 1.5.5 - 2019-04-18 =
* Fixed: Minor bug fixes.

= 1.5.4 - 2019-04-03 =
* Fixed: Fastimage.php library path.

= 1.5.3 - 2019-04-03 =
* Fixed: Missing template for notifications.
* Fixed: Missing template for GDPR.
* Fixed: Max input variable value under System Status.
* Fixed: Header and content wrapper overlapping when sticky header is enabled.
* Fixed: Links starting with `wp-content` no longer converted when AMP URL format is "End Point".

= 1.5.2 - 2019-03-27 =
* Fixed: Social links colors in sidebar.

= 1.5.1 - 2019-03-26 =
* Note: Disabled development mode.

= 1.5.0 - 2019-03-26 =
* Note: Front-end theme code refactoring.
* Fixed: Broken landing page for Custom Post Types when AMP URL format is "End Point".

= 1.4.3.1 - 2019-02-12 =
* Feature: Jetpack compatibility.
* Improved: All in One SEO Pack plugin support.
* Fixed: Malformed URLs when AMP URL format is "End Point".
* Fixed: Jetpack undefined error.
* Fixed: Broken layout when notice bar is enabled.
* Fixed: Broken layout when GDPR is enabled.

= 1.4.3 - 2019-01-26 =
* Feature: Yoast SEO meta description tag support.
* Fixed: YouTube video height issue.

= 1.4.2 - 2019-01-18 =
* Feature: Responsive table CSS.
* Note: Front-end theme improvements.

= 1.4.1 - 2019-01-04 =
* Feature: Two new header presets.
* Feature: Customizable header color schemes.
* Feature: Schema type for Post.
* Fixed: Exclude URLs list issue on all pages.
* Fixed: Home page link issue on all pages.
* Fixed: Permalink structure prefix causing 404 errors on Home and Search pages.

= 1.4.0 - 2018-12-12 =
* Feature: Gutenberg editor support.
* Feature: AMP at the end of the URL support.
* Feature: Show/Hide Related Post thumbnails.
* Feature: Show/Hide Social Links on Single Post pages.
* Feature: Enable/Disable structured data site-wide.
* Improved: Admin UI/UX.
* Fixed: Minor errors.

= 1.3.1 - 2018-12-03 =
* Feature: Show/Hide Date in Related Posts.
* Feature: Show/Hide Author in Related Posts.
* Feature: Show/Hide Date in Home Page Slider.
* Feature: Show/Hide Author in Home Page Slider.
* Feature: Home Page Slider post count setting.

= 1.3.0 - 2018-12-03 =
* Feature: Show/Hide Date on Archive/Single Post pages.
* Feature: Show/Hide Author on Archive/Single Post pages.
* Feature: Show/Hide Tags on Single Post pages.
* Note: Front-end structural improvements.
* Fixed: Social media share links with custom permalink structures.

= 1.2.2 - 2018-12-02 =
* Fixed: YouTube video embed error.

= 1.2.1 - 2018-11-29 =
* Note: Default configuration setup.
* Feature: New follow links - Pinterest, Instagram, LinkedIn, YouTube.
* Fixed: Minor errors.

= 1.2.0 - 2018-11-23 =
* Feature: Post Modified date display.
* Feature: Option to hide featured image on single pages.
* Note: Overall structural improvements.
* Note: Modified date now always included in structured data per updated Google requirements.
* Fixed: Search page action URL on HTTPS.
* Fixed: Invalid JSON for Yandex Metrica.
* Fixed: Custom HTML output in "Codes right after &lt;body&gt; tag".

= 1.1.1 - 2018-11-16 =
* Improved: Posts now show author display name instead of email or username.
* Fixed: Broken layout when social share is enabled from the Customizer.
* Fixed: Customizer preview refresh on option change in AMP Options Panel.

= 1.1.0 - 2018-11-10 =
* Improved: Settings panel revised.
* Fixed: 404 error on AMP WP activation.
* Fixed: Minor bugs.

= 1.0.5 =
* Fixed: Links for AMP Menus (Sidebar & Footer Menu).
* Fixed: Too many redirects.

= 1.0.4 =
* Added: Yandex Metrica, AFS Analytics, and Adobe Analytics support.
* Added: Squirrly SEO plugin compatibility.
* Fixed: Mobile users force redirect.

= 1.0.3 =
* Added: comScore UDM pageview tracking.
* Fixed: AMP page validation and minor bug fixes.

= 1.0.2 =
* Added: Alexa Metrics and Chartbeat Analytics support.
* Improved: CSS tweaks for pagination and buttons.

= 1.0.1 =
* Note: Minor RTL CSS tweaks.

= 1.0.0 =
* Initial release.
