This commit is contained in:
Javier Casares 2026-08-17 18:34:57 +00:00
commit b9029e62fe
14 changed files with 1750 additions and 946 deletions

View file

@ -31,6 +31,22 @@ $page_url = admin_url( 'options-general.php?page=robotstxt-og-settings' );
<?php settings_errors( 'robotstxt_og_messages' ); ?> <?php settings_errors( 'robotstxt_og_messages' ); ?>
<?php if ( ! Robotstxt_OG_Manager_Notice::is_manager_active() ) : ?>
<div class="notice notice-warning">
<p>
<?php
echo wp_kses_post(
sprintf(
/* translators: %s: Manager (by ROBOTSTXT) plugin page URL. */
__( 'To receive plugin updates, the <a href="%s">Manager (by ROBOTSTXT)</a> plugin must be installed and active.', 'robotstxt-og' ),
esc_url( Robotstxt_OG_Manager_Notice::MANAGER_URL )
)
);
?>
</p>
</div>
<?php endif; ?>
<nav class="nav-tab-wrapper" aria-label="<?php esc_attr_e( 'Settings tabs', 'robotstxt-og' ); ?>"> <nav class="nav-tab-wrapper" aria-label="<?php esc_attr_e( 'Settings tabs', 'robotstxt-og' ); ?>">
<a href="<?php echo esc_url( add_query_arg( 'tab', 'settings', $page_url ) ); ?>" <a href="<?php echo esc_url( add_query_arg( 'tab', 'settings', $page_url ) ); ?>"
class="nav-tab <?php echo 'settings' === $active_tab ? 'nav-tab-active' : ''; ?>"> class="nav-tab <?php echo 'settings' === $active_tab ? 'nav-tab-active' : ''; ?>">

View file

@ -1,5 +1,38 @@
== Changelog == == Changelog ==
= 1.2.1 =
_Release date: 2026-08-17_
**Highlights**
* Plugin updates are now delivered by Manager (by ROBOTSTXT) — the bundled Gitea self-updater was removed, and the plugin recommends the Manager on the Plugins page and its settings page when it is missing.
**Added**
* Dismissible admin notice on the Plugins list page when Manager (by ROBOTSTXT) is not installed and active, linking to https://www.robotstxt.software/plugins/robotstxt-manager/.
* Persistent (non-dismissible) Manager reminder on the plugin settings page when the Manager is not active.
* Uninstall cleanup for the legacy self-updater site transient.
**Changed**
* Removed the bundled self-updater (`robotstxt-updater.php`) and `update.json`; updates are delivered by Manager (by ROBOTSTXT).
* Plugin URI, Update URI, and author URLs now point to https://www.robotstxt.software/plugins/robotstxt-og/ and https://www.robotstxt.software/; contributors order is now robotstxt, javiercasares.
* Tested up to WordPress 7.1.
* Composer dev dependencies updated (PHPCS 3.13.6, WPCS 3.4.1, PHPUnit 9.6.36).
**Compatibility**
* WordPress: 4.9.8 - 7.1
* PHP: 8.0 - 8.5
* WP-CLI: 2.x
**Tests**
* PHP Coding Standards: 3.13.6
* WordPress Coding Standards: 3.4.1
* Plugin Check (PCP): not run (no WP-CLI in build environment)
= 1.2.0 = = 1.2.0 =
_Release date: 2026-08-10_ _Release date: 2026-08-10_
@ -35,7 +68,7 @@ _Release date: 2026-03-28_
**Added** **Added**
* GDPR Privacy API: registered `wp_privacy_personal_data_exporters` and `wp_privacy_personal_data_erasers` filters. Custom `_og_title` and `_og_description` post meta are now included in WordPress personal data export and erase requests. * GDPR Privacy API: registered `wp_privacy_personal_data_exporters` and `wp_privacy_personal_data_erasers` filters. Custom `_og_title` and `_og_description` post meta are now included in WordPress personal data export and erase requests.
* PHPUnit 10 test suite using Brain\Monkey for WordPress function mocking. Covers SSRF IP validation, image format detection (`ensure_compatible_format()`), external URL detection, and cache clearing. No WordPress installation required to run tests (`vendor/bin/phpunit`). * PHPUnit 9.6 test suite (with phpunit-polyfills) using Brain\Monkey for WordPress function mocking. Covers SSRF IP validation, image format detection (`ensure_compatible_format()`), external URL detection, and cache clearing. No WordPress installation required to run tests (`vendor/bin/phpunit`).
**Changed** **Changed**
@ -52,7 +85,7 @@ _Release date: 2026-03-28_
* WordPress Coding Standards: 3.3.0 * WordPress Coding Standards: 3.3.0
* PHPCompatibility: 9.3.5 * PHPCompatibility: 9.3.5
* PHPStan: level 9 * PHPStan: level 9
* PHPUnit: 10.5 * PHPUnit: 9.6
= 1.0.0 = = 1.0.0 =

View file

@ -77,6 +77,14 @@ class Robotstxt_OG_Image_Fallback {
*/ */
private Robotstxt_OG_Term_Meta $term_meta; private Robotstxt_OG_Term_Meta $term_meta;
/**
* Manager plugin notice instance.
*
* @since 1.2.1
* @var Robotstxt_OG_Manager_Notice|null
*/
private ?Robotstxt_OG_Manager_Notice $manager_notice = null;
/** /**
* Get singleton instance. * Get singleton instance.
* *
@ -140,6 +148,9 @@ class Robotstxt_OG_Image_Fallback {
if ( is_admin() ) { if ( is_admin() ) {
$this->admin_settings = new Robotstxt_OG_Admin_Settings( $this->resolver ); $this->admin_settings = new Robotstxt_OG_Admin_Settings( $this->resolver );
$this->admin_settings->init(); $this->admin_settings->init();
$this->manager_notice = new Robotstxt_OG_Manager_Notice();
$this->manager_notice->init();
} }
// Register WP-CLI commands. // Register WP-CLI commands.
@ -177,6 +188,7 @@ class Robotstxt_OG_Image_Fallback {
// Load admin class if in admin context. // Load admin class if in admin context.
if ( is_admin() ) { if ( is_admin() ) {
require_once ROBOTSTXT_OG_PATH . 'admin/class-robotstxt-og-admin-settings.php'; require_once ROBOTSTXT_OG_PATH . 'admin/class-robotstxt-og-admin-settings.php';
require_once ROBOTSTXT_OG_PATH . 'includes/class-robotstxt-og-manager-notice.php';
} }
// Load WP-CLI class if running in CLI context. // Load WP-CLI class if running in CLI context.

View file

@ -0,0 +1,170 @@
<?php
/**
* Manager Plugin Notice
*
* Recommends installing Manager (by ROBOTSTXT). Since the bundled
* self-updater was removed in 1.2.1, the Manager plugin provides updates
* for this plugin.
*
* @package ROBOTSTXT_OG
* @since 1.2.1
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Class Robotstxt_OG_Manager_Notice
*
* Shows a dismissible notice on the Plugins list page and a persistent
* message on the plugin settings page when Manager (by ROBOTSTXT) is not
* active.
*
* @since 1.2.1
*/
class Robotstxt_OG_Manager_Notice {
/**
* Basename of the Manager (by ROBOTSTXT) plugin.
*
* @since 1.2.1
* @var string
*/
const MANAGER_BASENAME = 'robotstxt-manager/robotstxt-manager.php';
/**
* Query argument used to dismiss the notice.
*
* @since 1.2.1
* @var string
*/
const DISMISS_ARG = 'robotstxt_og_dismiss_manager_notice';
/**
* Nonce action for the dismiss link.
*
* @since 1.2.1
* @var string
*/
const DISMISS_ACTION = 'robotstxt_og_dismiss_manager_notice';
/**
* User meta key storing the per-user dismissal.
*
* @since 1.2.1
* @var string
*/
const DISMISS_META = '_robotstxt_og_manager_notice_dismissed';
/**
* Manager plugin page URL.
*
* @since 1.2.1
* @var string
*/
const MANAGER_URL = 'https://www.robotstxt.software/plugins/robotstxt-manager/';
/**
* Whether the Manager (by ROBOTSTXT) plugin is installed and active.
*
* @since 1.2.1
*
* @return bool True if the Manager plugin is active.
*/
public static function is_manager_active(): bool {
return is_plugin_active( self::MANAGER_BASENAME );
}
/**
* Register hooks.
*
* @since 1.2.1
*
* @return void
*/
public function init(): void {
add_action( 'admin_init', array( $this, 'handle_dismiss' ) );
add_action( 'admin_notices', array( $this, 'render_notice' ) );
add_action( 'network_admin_notices', array( $this, 'render_notice' ) );
}
/**
* Persist the notice dismissal for the current user.
*
* Runs on admin_init; the notice check on admin_notices (fired later in
* the same request) then omits the notice immediately.
*
* @since 1.2.1
*
* @return void
*/
public function handle_dismiss(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET[ self::DISMISS_ARG ] ) ) {
return;
}
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
check_admin_referer( self::DISMISS_ACTION );
update_user_meta( get_current_user_id(), self::DISMISS_META, 1 );
}
/**
* Render the dismissible notice on the Plugins list page.
*
* Hooked to both `admin_notices` (single-site Plugins page) and
* `network_admin_notices` (Multisite network Plugins page); both screens
* share the `plugins` screen base.
*
* @since 1.2.1
*
* @return void
*/
public function render_notice(): void {
$screen = get_current_screen();
if ( null === $screen || 'plugins' !== $screen->base ) {
return;
}
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
if ( self::is_manager_active() ) {
return;
}
if ( get_user_meta( get_current_user_id(), self::DISMISS_META, true ) ) {
return;
}
$dismiss_url = wp_nonce_url(
add_query_arg( array( self::DISMISS_ARG => 1 ) ),
self::DISMISS_ACTION
);
?>
<div class="notice notice-info">
<p>
<?php
echo wp_kses_post(
sprintf(
/* translators: %s: Manager (by ROBOTSTXT) plugin page URL. */
__( 'To receive plugin updates, the <a href="%s">Manager (by ROBOTSTXT)</a> plugin must be installed and active.', 'robotstxt-og' ),
esc_url( self::MANAGER_URL )
)
);
?>
</p>
<p>
<a class="button" href="<?php echo esc_url( $dismiss_url ); ?>"><?php esc_html_e( 'Dismiss this notice', 'robotstxt-og' ); ?></a>
</p>
</div>
<?php
}
}

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -2,16 +2,13 @@
# This file is distributed under the GPL v3 or later. # This file is distributed under the GPL v3 or later.
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: OpenGraph (by ROBOTSTXT) 1.0.0\n" "Project-Id-Version: OpenGraph (by ROBOTSTXT) 1.2.1\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-og\n" "Report-Msgid-Bugs-To: https://git.robotstxt.es/ROBOTSTXT/robotstxt-og/issues\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"POT-Creation-Date: 2026-02-18T06:52:54+00:00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"X-Generator: WP-CLI 2.12.0\n"
"X-Domain: robotstxt-og\n" "X-Domain: robotstxt-og\n"
#. Plugin Name of the plugin #. Plugin Name of the plugin
@ -21,7 +18,7 @@ msgstr ""
#. Plugin URI of the plugin #. Plugin URI of the plugin
#: robotstxt-og.php #: robotstxt-og.php
msgid "https://git.robotstxt.es/ROBOTSTXT/robotstxt-og" msgid "https://www.robotstxt.software/plugins/robotstxt-og/"
msgstr "" msgstr ""
#. Description of the plugin #. Description of the plugin
@ -36,333 +33,470 @@ msgstr ""
#. Author URI of the plugin #. Author URI of the plugin
#: robotstxt-og.php #: robotstxt-og.php
msgid "https://www.robotstxt.es/" msgid "https://www.robotstxt.software/"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:102
#: admin/class-robotstxt-og-admin-settings.php:84 #: admin/class-robotstxt-og-admin-settings.php:1056
#: admin/class-robotstxt-og-admin-settings.php:526 #: admin/class-robotstxt-og-admin-settings.php:1103
#: admin/class-robotstxt-og-admin-settings.php:573
msgid "Security check failed" msgid "Security check failed"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:89 #: admin/class-robotstxt-og-admin-settings.php:107
#: admin/class-robotstxt-og-admin-settings.php:500 #: admin/class-robotstxt-og-admin-settings.php:1030
#: admin/class-robotstxt-og-admin-settings.php:531 #: admin/class-robotstxt-og-admin-settings.php:1061
#: admin/class-robotstxt-og-admin-settings.php:578 #: admin/class-robotstxt-og-admin-settings.php:1108
msgid "You do not have sufficient permissions to access this page." msgid "You do not have sufficient permissions to access this page."
msgstr "" msgstr ""
#. translators: %d: post ID #: admin/class-robotstxt-og-admin-settings.php:118
#: admin/class-robotstxt-og-admin-settings.php:100
#, php-format #, php-format
msgid "Successfully cleared cached fallback URL for post #%d." msgid "Successfully cleared cached fallback URL for post #%d."
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:121 #: admin/class-robotstxt-og-admin-settings.php:139
msgid "OpenGraph Settings" msgid "OpenGraph Settings"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:122 #: admin/class-robotstxt-og-admin-settings.php:140
msgid "OpenGraph" msgid "OpenGraph"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:211 #: admin/class-robotstxt-og-admin-settings.php:349
msgid "General Settings" msgid "General Settings"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:218 #: admin/class-robotstxt-og-admin-settings.php:356
msgid "Global Fallback Image" msgid "Global Fallback Image"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:226 #: admin/class-robotstxt-og-admin-settings.php:364
#: admin/class-robotstxt-og-admin-settings.php:347
msgid "Homepage Image" msgid "Homepage Image"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:234 #: admin/class-robotstxt-og-admin-settings.php:372
msgid "Data Management" msgid "Data Management"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:243 #: admin/class-robotstxt-og-admin-settings.php:381
msgid "Social Media Tags" msgid "Social Media Tags"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:250 #: admin/class-robotstxt-og-admin-settings.php:388
msgid "Facebook / OG Tags" msgid "Facebook / OG Tags"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:258 #: admin/class-robotstxt-og-admin-settings.php:396
msgid "Twitter Card Tags" msgid "Twitter Card Tags"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:266 #: admin/class-robotstxt-og-admin-settings.php:404
msgid "Twitter Card Type" msgid "Twitter Card Type"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:274 #: admin/class-robotstxt-og-admin-settings.php:412
msgid "Twitter/X Site Handle" msgid "Twitter/X Site Handle"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:290 #: admin/class-robotstxt-og-admin-settings.php:421
msgid "Configure OpenGraph image fallback behavior." msgid "Platform Integration"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:311 #: admin/class-robotstxt-og-admin-settings.php:428
msgid "Fallback Image" msgid "Facebook App ID"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:316 #: admin/class-robotstxt-og-admin-settings.php:436
#: admin/class-robotstxt-og-admin-settings.php:352 msgid "Facebook Admins"
#: admin/class-robotstxt-og-admin-settings.php:699
msgid "Select Image"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:320 #: admin/class-robotstxt-og-admin-settings.php:444
#: admin/class-robotstxt-og-admin-settings.php:356 msgid "Facebook Pages"
msgid "Remove Image"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:324 #: admin/class-robotstxt-og-admin-settings.php:452
msgid "Used as Open Graph image when a post has no featured image or when no compatible format (JPEG/PNG) is found." msgid "Pinterest Domain Verification"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:360 #: admin/class-robotstxt-og-admin-settings.php:460
msgid "Dedicated Open Graph image for the site homepage. Falls back to the Global Fallback Image if not set." msgid "Disable Pinterest Pinning"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:378 #: admin/class-robotstxt-og-admin-settings.php:468
msgid "Delete all plugin data when uninstalling" msgid "Telegram Channel"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:381 #: admin/class-robotstxt-og-admin-settings.php:476
msgid "Warning: This will permanently delete all cached fallback URLs and plugin settings. This action cannot be undone." msgid "Slack App ID"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:395
msgid "Choose which social media meta tag groups to output and configure their behavior."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:411
msgid "Enable Facebook / Open Graph image tags"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:414
msgid "Outputs og:title, og:type, og:url, og:description, og:site_name, og:locale, og:image, og:image:width, og:image:height, og:image:type, and og:image:alt meta tags."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:431
msgid "Enable Twitter Card image tags"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:434
msgid "Outputs twitter:card, twitter:site, and twitter:image meta tags."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:449
msgid "Summary with large image"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:450
msgid "Summary (small image)"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:461
msgid "Controls the twitter:card meta tag value. \"Summary with large image\" is recommended for most sites."
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:485 #: admin/class-robotstxt-og-admin-settings.php:485
msgid "Your site's Twitter/X account handle (e.g. @example). Used for the twitter:site meta tag." msgid "Mobile / Web App"
msgstr "" msgstr ""
#. translators: %d: number of cleared cache entries #: admin/class-robotstxt-og-admin-settings.php:492
#: admin/class-robotstxt-og-admin-settings.php:542 msgid "Theme Color"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:500
msgid "Web App Name"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:508
msgid "PWA Standalone Mode"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:516
msgid "iOS Status Bar Style"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:524
msgid "Disable Auto Phone Detection"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:555
msgid ""
"The image URL is not valid. Please enter a full URL starting with https://."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:567
msgid ""
"The image URL is not in a compatible format (JPEG or PNG), and no compatible "
"alternative could be found."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:585
msgid "Configure OpenGraph image fallback behavior."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:609
msgid ""
"Used as Open Graph image when a post has no featured image or when no "
"compatible format (JPEG/PNG) is found. Must be a direct URL to a JPEG or PNG "
"file."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:634
msgid ""
"Dedicated Open Graph image for the site homepage. Falls back to the Global "
"Fallback Image if not set. Must be a direct URL to a JPEG or PNG file."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:651
msgid "Delete all plugin data when uninstalling"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:654
msgid ""
"Warning: This will permanently delete all cached fallback URLs and plugin "
"settings. This action cannot be undone."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:668
msgid ""
"Choose which social media meta tag groups to output and configure their "
"behavior."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:684
msgid "Enable Facebook / Open Graph image tags"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:687
msgid ""
"Outputs og:title, og:type, og:url, og:description, og:site_name, og:locale, "
"og:image, og:image:width, og:image:height, og:image:type, and og:image:alt "
"meta tags."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:704
msgid "Enable Twitter Card image tags"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:707
msgid "Outputs twitter:card, twitter:site, and twitter:image meta tags."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:722
msgid "Summary with large image"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:723
msgid "Summary (small image)"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:734
msgid ""
"Controls the twitter:card meta tag value. \"Summary with large image\" is "
"recommended for most sites."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:759
msgid ""
"Your site's Twitter/X account handle (e.g. @example). Used for the twitter:"
"site meta tag."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:774
msgid ""
"Site-wide identity and verification tags for Facebook, Pinterest, Telegram, "
"and Slack. Emitted in addition to the Open Graph / Twitter tags when no SEO "
"plugin is handling them."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:813
msgid "Facebook App ID, used for the fb:app_id meta tag and Insights."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:824
msgid "Comma-separated Facebook user IDs of page admins (fb:admins)."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:835
msgid "Facebook Page ID or comma-separated IDs (fb:pages)."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:846
msgid "Pinterest domain verification token (p:domain_verify)."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:867
msgid ""
"Emit the Pinterest \"nopin\" meta tag to discourage pinning images from this "
"site."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:880
msgid "Telegram channel handle (telegram:channel), including the leading @."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:891
msgid "Slack App ID for custom unfurls (slack-app-id)."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:919
msgid ""
"Optional mobile and Progressive Web App metadata. Tags are emitted only when "
"configured."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:957
msgid "Hex color for the browser UI chrome (theme-color)."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:968
msgid ""
"Web app name; emitted as application-name and apple-mobile-web-app-title."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:979
msgid ""
"Emit mobile-web-app-capable and apple-mobile-web-app-capable (standalone PWA "
"mode)."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:993
msgid "Default (omit tag)"
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:1017
msgid "Emit format-detection to disable automatic phone-number linking."
msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:1072
#, php-format #, php-format
msgid "Successfully cleared %d cached fallback URLs." msgid "Successfully cleared %d cached fallback URLs."
msgstr "" msgstr ""
#. translators: 1: number of successful resolutions 2: number of failed resolutions #: admin/class-robotstxt-og-admin-settings.php:1119
#: admin/class-robotstxt-og-admin-settings.php:589
#, php-format #, php-format
msgid "Batch resolution complete. Success: %1$d, Failed: %2$d" msgid "Batch resolution complete. Success: %1$d, Failed: %2$d"
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:700 #: admin/class-robotstxt-og-admin-settings.php:1229
msgid "Use This Image" msgid ""
"Are you sure you want to clear all cached fallback URLs? This action cannot "
"be undone."
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:701 #: admin/class-robotstxt-og-admin-settings.php:1230
msgid "Are you sure you want to clear all cached fallback URLs? This action cannot be undone." msgid ""
"Are you sure you want to re-resolve all images? This may take some time on "
"large sites."
msgstr "" msgstr ""
#: admin/class-robotstxt-og-admin-settings.php:702 #: admin/views/settings-page.php:41
msgid "Are you sure you want to re-resolve all images? This may take some time on large sites." #: includes/class-robotstxt-og-manager-notice.php:153
#, php-format
msgid ""
"To receive plugin updates, the <a href=\"%s\">Manager (by ROBOTSTXT)</a> "
"plugin must be installed and active."
msgstr "" msgstr ""
#: admin/views/settings-page.php:33 #: admin/views/settings-page.php:50
msgid "Settings tabs" msgid "Settings tabs"
msgstr "" msgstr ""
#: admin/views/settings-page.php:36 #: admin/views/settings-page.php:53
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
#: admin/views/settings-page.php:40 #: admin/views/settings-page.php:57
msgid "Tools" msgid "Tools"
msgstr "" msgstr ""
#: admin/views/settings-page.php:44 #: admin/views/settings-page.php:61
msgid "Diagnostics" msgid "Diagnostics"
msgstr "" msgstr ""
#: admin/views/settings-page.php:56 #: admin/views/settings-page.php:73
msgid "Save Settings" msgid "Save Settings"
msgstr "" msgstr ""
#: admin/views/settings-page.php:62 #: admin/views/settings-page.php:79
msgid "Cache Management" msgid "Cache Management"
msgstr "" msgstr ""
#: admin/views/settings-page.php:64 #: admin/views/settings-page.php:81
msgid "Use these tools to manage cached fallback image URLs." msgid "Use these tools to manage cached fallback image URLs."
msgstr "" msgstr ""
#: admin/views/settings-page.php:70 #: admin/views/settings-page.php:87
msgid "Clear All Caches" msgid "Clear All Caches"
msgstr "" msgstr ""
#: admin/views/settings-page.php:89 #: admin/views/settings-page.php:106
msgid "Clear All Cached URLs" msgid "Clear All Cached URLs"
msgstr "" msgstr ""
#: admin/views/settings-page.php:92 #: admin/views/settings-page.php:109
msgid "Delete all cached fallback URLs. Images will be re-resolved on next page view." msgid ""
"Delete all cached fallback URLs. Images will be re-resolved on next page "
"view."
msgstr "" msgstr ""
#: admin/views/settings-page.php:98 #: admin/views/settings-page.php:115
msgid "Re-resolve All Images" msgid "Re-resolve All Images"
msgstr "" msgstr ""
#: admin/views/settings-page.php:117 #: admin/views/settings-page.php:134
msgid "Re-resolve All Images Now" msgid "Re-resolve All Images Now"
msgstr "" msgstr ""
#: admin/views/settings-page.php:120 #: admin/views/settings-page.php:137
msgid "Clear cache and immediately re-resolve all posts with featured images. May take time on large sites." msgid ""
msgstr "" "Clear cache and immediately re-resolve all posts with featured images. May "
"take time on large sites."
#: admin/views/settings-page.php:127
msgid "Plugin Information"
msgstr ""
#: admin/views/settings-page.php:132
msgid "Version"
msgstr ""
#: admin/views/settings-page.php:136
msgid "Documentation"
msgstr ""
#: admin/views/settings-page.php:139
msgid "View Documentation"
msgstr "" msgstr ""
#: admin/views/settings-page.php:144 #: admin/views/settings-page.php:144
msgid "Plugin Information"
msgstr ""
#: admin/views/settings-page.php:149
msgid "Version"
msgstr ""
#: admin/views/settings-page.php:153
msgid "Documentation"
msgstr ""
#: admin/views/settings-page.php:156
msgid "View Documentation"
msgstr ""
#: admin/views/settings-page.php:161
msgid "Support" msgid "Support"
msgstr "" msgstr ""
#: admin/views/settings-page.php:147 #: admin/views/settings-page.php:164
msgid "Report an Issue" msgid "Report an Issue"
msgstr "" msgstr ""
#: admin/views/settings-page.php:194 #: admin/views/settings-page.php:215
msgid "Statistics" msgid "Statistics"
msgstr "" msgstr ""
#: admin/views/settings-page.php:199 #: admin/views/settings-page.php:220
msgid "Posts with featured images" msgid "Posts with featured images"
msgstr "" msgstr ""
#: admin/views/settings-page.php:203 #: admin/views/settings-page.php:224
msgid "Posts with cached fallback URLs" msgid "Posts with cached fallback URLs"
msgstr "" msgstr ""
#: admin/views/settings-page.php:207 #: admin/views/settings-page.php:228
msgid "Coverage" msgid "Coverage"
msgstr "" msgstr ""
#: admin/views/settings-page.php:213 #: admin/views/settings-page.php:234
msgid "N/A" msgid "N/A"
msgstr "" msgstr ""
#: admin/views/settings-page.php:221 #: admin/views/settings-page.php:242 admin/views/settings-page.php:269
#: admin/views/settings-page.php:247
msgid "Test URL" msgid "Test URL"
msgstr "" msgstr ""
#: admin/views/settings-page.php:223 #: admin/views/settings-page.php:244
msgid "Test whether a URL is reachable via HTTP HEAD request." msgid "Test whether a URL is reachable via HTTP HEAD request."
msgstr "" msgstr ""
#: admin/views/settings-page.php:232 #: admin/views/settings-page.php:253
msgid "URL to test" msgid "URL to test"
msgstr "" msgstr ""
#: admin/views/settings-page.php:267 #: admin/views/settings-page.php:289
msgid "Test Result" msgid "Test Result"
msgstr "" msgstr ""
#: admin/views/settings-page.php:271 #: admin/views/settings-page.php:293
msgid "Error:" msgid "Error:"
msgstr "" msgstr ""
#: admin/views/settings-page.php:279 #: admin/views/settings-page.php:301
msgid "HTTP Status:" msgid "HTTP Status:"
msgstr "" msgstr ""
#: admin/views/settings-page.php:285 #: admin/views/settings-page.php:307
msgid "Header" msgid "Header"
msgstr "" msgstr ""
#: admin/views/settings-page.php:286 #: admin/views/settings-page.php:308
msgid "Value" msgid "Value"
msgstr "" msgstr ""
#. translators: %d: total entries #: admin/views/settings-page.php:330
#: admin/views/settings-page.php:301
#, php-format #, php-format
msgid "Cached Fallback URLs (%d)" msgid "Cached Fallback URLs (%d)"
msgstr "" msgstr ""
#: admin/views/settings-page.php:306 #: admin/views/settings-page.php:335 includes/class-robotstxt-og-cli.php:292
#: includes/class-robotstxt-og-cli.php:289
msgid "No cached fallback URLs found." msgid "No cached fallback URLs found."
msgstr "" msgstr ""
#: admin/views/settings-page.php:312 #: admin/views/settings-page.php:341
msgid "Post ID" msgid "Post ID"
msgstr "" msgstr ""
#: admin/views/settings-page.php:313 #: admin/views/settings-page.php:342
msgid "Title" msgid "Title"
msgstr "" msgstr ""
#: admin/views/settings-page.php:314 #: admin/views/settings-page.php:343
msgid "Type" msgid "Type"
msgstr "" msgstr ""
#: admin/views/settings-page.php:315 #: admin/views/settings-page.php:344
msgid "Cached Fallback URL" msgid "Cached Fallback URL"
msgstr "" msgstr ""
#: admin/views/settings-page.php:316 #: admin/views/settings-page.php:345
msgid "Actions" msgid "Actions"
msgstr "" msgstr ""
#: admin/views/settings-page.php:351 #: admin/views/settings-page.php:380
msgid "Clear" msgid "Clear"
msgstr "" msgstr ""
#. translators: %d: number of posts found
#: includes/class-robotstxt-og-cli.php:187 #: includes/class-robotstxt-og-cli.php:187
#, php-format #, php-format
msgid "Found %d posts with featured images. (dry-run, no changes made)" msgid "Found %d posts with featured images. (dry-run, no changes made)"
@ -372,90 +506,150 @@ msgstr ""
msgid "No posts with featured images found." msgid "No posts with featured images found."
msgstr "" msgstr ""
#. translators: %d: number of posts to process
#: includes/class-robotstxt-og-cli.php:201 #: includes/class-robotstxt-og-cli.php:201
#, php-format #, php-format
msgid "Resolving %d posts" msgid "Resolving %d posts"
msgstr "" msgstr ""
#. translators: 1: successful count, 2: failed count
#: includes/class-robotstxt-og-cli.php:222 #: includes/class-robotstxt-og-cli.php:222
#, php-format #, php-format
msgid "Resolved %1$d posts. Failed: %2$d." msgid "Resolved %1$d posts. Failed: %2$d."
msgstr "" msgstr ""
#. translators: %d: post ID
#: includes/class-robotstxt-og-cli.php:240 #: includes/class-robotstxt-og-cli.php:240
#, php-format #, php-format
msgid "Post %d not found." msgid "Post %d not found."
msgstr "" msgstr ""
#. translators: 1: post ID, 2: image URL
#: includes/class-robotstxt-og-cli.php:248 #: includes/class-robotstxt-og-cli.php:248
#, php-format #, php-format
msgid "Post %1$d has featured image: %2$s (dry-run, no changes made)" msgid "Post %1$d has featured image: %2$s (dry-run, no changes made)"
msgstr "" msgstr ""
#. translators: 1: post ID, 2: resolved URL
#: includes/class-robotstxt-og-cli.php:257 #: includes/class-robotstxt-og-cli.php:257
#, php-format #, php-format
msgid "Post %1$d resolved to %2$s" msgid "Post %1$d resolved to %2$s"
msgstr "" msgstr ""
#. translators: %d: post ID
#: includes/class-robotstxt-og-cli.php:260 #: includes/class-robotstxt-og-cli.php:260
#, php-format #, php-format
msgid "Post %d: no compatible image found." msgid "Post %d: no compatible image found."
msgstr "" msgstr ""
#. translators: %d: number of cache entries found #: includes/class-robotstxt-og-cli.php:287
#: includes/class-robotstxt-og-cli.php:284
#, php-format #, php-format
msgid "Found %d cached fallback URLs. (dry-run, no changes made)" msgid "Found %d cached fallback URLs. (dry-run, no changes made)"
msgstr "" msgstr ""
#. translators: %d: number of cleared entries #: includes/class-robotstxt-og-cli.php:306
#: includes/class-robotstxt-og-cli.php:300
#, php-format #, php-format
msgid "Cleared %d cached fallback URLs." msgid "Cleared %d cached fallback URLs."
msgstr "" msgstr ""
#. translators: 1: post ID, 2: cached URL or 'none' #: includes/class-robotstxt-og-cli.php:323
#: includes/class-robotstxt-og-cli.php:317
#, php-format #, php-format
msgid "Post %1$d cached URL: %2$s (dry-run, no changes made)" msgid "Post %1$d cached URL: %2$s (dry-run, no changes made)"
msgstr "" msgstr ""
#. translators: %d: post ID #: includes/class-robotstxt-og-cli.php:329
#: includes/class-robotstxt-og-cli.php:323
#, php-format #, php-format
msgid "Post %d has no cached fallback URL." msgid "Post %d has no cached fallback URL."
msgstr "" msgstr ""
#. translators: %d: post ID #: includes/class-robotstxt-og-cli.php:335
#: includes/class-robotstxt-og-cli.php:329
#, php-format #, php-format
msgid "Cleared cached fallback URL for post %d." msgid "Cleared cached fallback URL for post %d."
msgstr "" msgstr ""
#: includes/class-robotstxt-og-meta-box.php:101 #: includes/class-robotstxt-og-image-fallback.php:268
#: includes/class-robotstxt-og-image-fallback.php:284
msgid "OpenGraph Custom Post Data"
msgstr ""
#: includes/class-robotstxt-og-image-fallback.php:339
#: includes/class-robotstxt-og-term-meta.php:219
msgid "Custom OG Title"
msgstr ""
#: includes/class-robotstxt-og-image-fallback.php:346
#: includes/class-robotstxt-og-term-meta.php:270
msgid "Custom OG Description"
msgstr ""
#: includes/class-robotstxt-og-image-fallback.php:353
#: includes/class-robotstxt-og-meta-box.php:225
msgid "Twitter Author Handle"
msgstr ""
#: includes/class-robotstxt-og-image-fallback.php:360
#: includes/class-robotstxt-og-meta-box.php:282
msgid "Custom OG Image Alt"
msgstr ""
#: includes/class-robotstxt-og-image-fallback.php:368
msgid "OpenGraph Post Meta"
msgstr ""
#: includes/class-robotstxt-og-manager-notice.php:160
msgid "Dismiss this notice"
msgstr ""
#: includes/class-robotstxt-og-meta-box.php:166
msgid "Open Graph / Social Media" msgid "Open Graph / Social Media"
msgstr "" msgstr ""
#: includes/class-robotstxt-og-meta-box.php:130 #: includes/class-robotstxt-og-meta-box.php:202
msgid "Open Graph Type"
msgstr ""
#: includes/class-robotstxt-og-meta-box.php:208
msgid "Default (auto: article for posts, website otherwise)"
msgstr ""
#: includes/class-robotstxt-og-meta-box.php:211
msgid "website"
msgstr ""
#: includes/class-robotstxt-og-meta-box.php:214
msgid "article"
msgstr ""
#: includes/class-robotstxt-og-meta-box.php:218
msgid ""
"Overrides og:type for this post. Selecting \"article\" also emits the "
"article:* meta tags (published/modified time, section, tags)."
msgstr ""
#: includes/class-robotstxt-og-meta-box.php:238
msgid ""
"Twitter/X handle of the post author, emitted as twitter:creator. Leave blank "
"to omit."
msgstr ""
#: includes/class-robotstxt-og-meta-box.php:245
msgid "Custom Title" msgid "Custom Title"
msgstr "" msgstr ""
#: includes/class-robotstxt-og-meta-box.php:142 #: includes/class-robotstxt-og-meta-box.php:257
msgid "Overrides the default og:title for this post. Leave blank to use the post title automatically." msgid ""
"Overrides the default og:title for this post. Leave blank to use the post "
"title automatically."
msgstr "" msgstr ""
#: includes/class-robotstxt-og-meta-box.php:149 #: includes/class-robotstxt-og-meta-box.php:264
msgid "Custom Description" msgid "Custom Description"
msgstr "" msgstr ""
#: includes/class-robotstxt-og-meta-box.php:160 #: includes/class-robotstxt-og-meta-box.php:275
msgid "Overrides the default og:description for this post. Leave blank to use the excerpt automatically." msgid ""
"Overrides the default og:description for this post. Leave blank to use the "
"excerpt automatically."
msgstr ""
#: includes/class-robotstxt-og-meta-box.php:294
msgid ""
"Alt text for the og:image of this post. Leave blank to use the featured "
"image alt text from the media library."
msgstr "" msgstr ""
#: includes/class-robotstxt-og-rest-api.php:85 #: includes/class-robotstxt-og-rest-api.php:85
@ -466,11 +660,47 @@ msgstr ""
msgid "The post ID to get the fallback image status for." msgid "The post ID to get the fallback image status for."
msgstr "" msgstr ""
#: includes/class-robotstxt-og-rest-api.php:132 #: includes/class-robotstxt-og-rest-api.php:133
#: includes/class-robotstxt-og-rest-api.php:170 #: includes/class-robotstxt-og-rest-api.php:172
msgid "Post not found." msgid "Post not found."
msgstr "" msgstr ""
#: includes/class-robotstxt-og-rest-api.php:202 #: includes/class-robotstxt-og-rest-api.php:205
msgid "You do not have permission to access this endpoint." msgid "You do not have permission to access this endpoint."
msgstr "" msgstr ""
#: includes/class-robotstxt-og-rest-api.php:217
msgid "You do not have permission to edit this post."
msgstr ""
#: includes/class-robotstxt-og-term-meta.php:230
msgid ""
"Overrides og:title for this term archive. Leave blank to use the term name."
msgstr ""
#: includes/class-robotstxt-og-term-meta.php:236
msgid "OG Image URL"
msgstr ""
#: includes/class-robotstxt-og-term-meta.php:247
msgid ""
"Absolute HTTPS URL for og:image on this term archive. JPEG or PNG "
"recommended."
msgstr ""
#: includes/class-robotstxt-og-term-meta.php:253
msgid "OG Image Alt"
msgstr ""
#: includes/class-robotstxt-og-term-meta.php:264
msgid ""
"Alt text for the term archive og:image. Leave blank to use the media library "
"alt text."
msgstr ""
#: includes/class-robotstxt-og-term-meta.php:280
msgid ""
"Overrides og:description for this term archive. Leave blank to use the term "
"description."
msgstr ""

View file

@ -1,11 +1,11 @@
=== OpenGraph (by ROBOTSTXT) === === OpenGraph (by ROBOTSTXT) ===
Contributors: javiercasares, robotstxt Contributors: robotstxt, javiercasares
Tags: opengraph, open graph, twitter card, social media, seo Tags: opengraph, open graph, twitter card, social media, seo
Requires at least: 4.9.8 Requires at least: 4.9.8
Tested up to: 7.0 Tested up to: 7.1
Stable tag: 1.2.0 Stable tag: 1.2.1
Requires PHP: 8.0 Requires PHP: 8.0
Version: 1.2.0 Version: 1.2.1
License: GPL-3.0-or-later License: GPL-3.0-or-later
License URI: https://www.gnu.org/licenses/gpl-3.0.txt License URI: https://www.gnu.org/licenses/gpl-3.0.txt
@ -117,7 +117,7 @@ Developer filters available in `functions.php` or a custom plugin:
= Automatic download = = Automatic download =
Visit the plugin section in your WordPress dashboard, search for **OpenGraph by ROBOTSTXT**, then download and install the plugin. Download the plugin ZIP from [robotstxt.software](https://www.robotstxt.software/plugins/robotstxt-og/), install it from your WordPress dashboard (Plugins > Add New > Upload), and activate it. To receive automatic updates, install and activate [Manager (by ROBOTSTXT)](https://www.robotstxt.software/plugins/robotstxt-manager/).
= Manual download = = Manual download =
@ -134,6 +134,10 @@ Extract the contents of the ZIP and upload the folder to the `/wp-content/plugin
== Frequently Asked Questions == == Frequently Asked Questions ==
= How do I get updates for this plugin? =
Updates are delivered by [Manager (by ROBOTSTXT)](https://www.robotstxt.software/plugins/robotstxt-manager/). When the Manager plugin is not installed and active, this plugin shows a dismissible notice on the Plugins page and a reminder on its settings page.
= Does this plugin duplicate og:image tags from Yoast SEO or RankMath? = = Does this plugin duplicate og:image tags from Yoast SEO or RankMath? =
No. When Yoast SEO or RankMath is active, the plugin hooks into their existing filter (`wpseo_opengraph_image` or `rank_math/opengraph/facebook/og_image`) instead of injecting tags directly. The result is a single `og:image` tag, with the resolved fallback applied only when needed. No. When Yoast SEO or RankMath is active, the plugin hooks into their existing filter (`wpseo_opengraph_image` or `rank_math/opengraph/facebook/og_image`) instead of injecting tags directly. The result is a single `og:image` tag, with the resolved fallback applied only when needed.
@ -164,7 +168,7 @@ Yes. The plugin hooks into `wp_head` for direct tag injection and filters Yoast/
== Compatibility == == Compatibility ==
* WordPress: 4.9.8 - 7.0 * WordPress: 4.9.8 - 7.1
* PHP: 8.0 - 8.5 * PHP: 8.0 - 8.5
* WP-CLI: 2.x * WP-CLI: 2.x
* MariaDB: 10.6+ * MariaDB: 10.6+
@ -176,6 +180,15 @@ Yes. The plugin hooks into `wp_head` for direct tag injection and filters Yoast/
== Changelog == == Changelog ==
= 1.2.1 =
_Release date: 2026-08-17_
* Added: Dismissible notice on the Plugins page recommending Manager (by ROBOTSTXT) when it is not installed and active, plus a persistent reminder on the plugin settings page.
* Changed: Plugin updates are now delivered by Manager (by ROBOTSTXT); the bundled Gitea self-updater (`robotstxt-updater.php`) and `update.json` were removed.
* Changed: Plugin home, update, and author URLs now point to `https://www.robotstxt.software/plugins/robotstxt-og/`.
* Changed: Tested up to WordPress 7.1.
= 1.2.0 = = 1.2.0 =
_Release date: 2026-08-10_ _Release date: 2026-08-10_
@ -203,28 +216,9 @@ _Release date: 2026-03-28_
* Changed: Added `Network: true` header confirming Multisite compatibility. * Changed: Added `Network: true` header confirming Multisite compatibility.
* Added: PHPUnit test suite (Brain\Monkey) covering SSRF protection, format detection, and cache clearing. * Added: PHPUnit test suite (Brain\Monkey) covering SSRF protection, format detection, and cache clearing.
= 1.0.0 =
_Release date: 2026-02-18_
* Initial stable release.
* Full Open Graph meta tag output: `og:title`, `og:type`, `og:url`, `og:description`, `og:site_name`, `og:locale`, `og:image`, `og:image:width`, `og:image:height`, `og:image:type`, `og:image:alt`.
* Article-specific tags for singular posts: `article:published_time`, `article:modified_time`, `article:section`, `article:tag`.
* Twitter Card tags: `twitter:card`, `twitter:site`, `twitter:creator`, `twitter:url`, `twitter:image`, `twitter:image:alt`.
* Automatic format detection (AVIF, WebP, GIF, BMP, SVG, TIFF) with HTTP HEAD-based JPEG/PNG fallback resolution.
* Postmeta caching with automatic invalidation on featured image change.
* Per-post OG title and description overrides via post editor meta box.
* Global fallback image URL and homepage-specific image URL settings.
* Yoast SEO and RankMath integration (no duplicate tags).
* Admin settings page with Settings, Tools, and Diagnostics tabs.
* WP-CLI commands: `resolve` and `clear-cache`.
* REST API endpoints: `POST /resolve/{id}` and `GET /status/{id}`.
* Negative caching via transients (1-hour TTL) for failed URL checks.
* Multisite compatible.
= Previous versions = = Previous versions =
This is the first release. For the full changelog, visit the [changelog.txt](https://git.robotstxt.es/ROBOTSTXT/robotstxt-og/raw/branch/main/changelog.txt) file. If you want to see the full changelog, visit the [changelog](https://www.robotstxt.software/plugins/robotstxt-og/) page.
== Compliance == == Compliance ==

View file

@ -1,23 +1,23 @@
<?php <?php
/** /**
* Plugin Name: OpenGraph (by ROBOTSTXT) * Plugin Name: OpenGraph (by ROBOTSTXT)
* Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-og * Plugin URI: https://www.robotstxt.software/plugins/robotstxt-og/
* Description: Intelligent Open Graph image fallback for social media crawlers. Automatically detects and serves compatible image formats (JPEG/PNG) when modern formats (AVIF/WebP) are used as featured images. * Description: Intelligent Open Graph image fallback for social media crawlers. Automatically detects and serves compatible image formats (JPEG/PNG) when modern formats (AVIF/WebP) are used as featured images.
* Version: 1.2.0 * Version: 1.2.1
* Requires at least: 4.9.8 * Requires at least: 4.9.8
* Requires PHP: 8.0 * Requires PHP: 8.0
* Author: ROBOTSTXT * Author: ROBOTSTXT
* Author URI: https://www.robotstxt.es/ * Author URI: https://www.robotstxt.software/
* License: GPL v3 or later * License: GPL v3 or later
* License URI: https://www.gnu.org/licenses/gpl-3.0.html * License URI: https://www.gnu.org/licenses/gpl-3.0.html
* Text Domain: robotstxt-og * Text Domain: robotstxt-og
* Domain Path: /languages * Domain Path: /languages
* Network: true * Network: true
* Gitea Plugin URI: ROBOTSTXT/robotstxt-og * Update URI: https://www.robotstxt.software/plugins/robotstxt-og/
* Contributors: javiercasares, robotstxt * Contributors: robotstxt, javiercasares
* *
* @package ROBOTSTXT_OG * @package ROBOTSTXT_OG
* @version 1.2.0 * @version 1.2.1
*/ */
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
@ -25,15 +25,11 @@ if ( ! defined( 'ABSPATH' ) ) {
} }
// Define plugin constants. // Define plugin constants.
define( 'ROBOTSTXT_OG_VERSION', '1.2.0' ); define( 'ROBOTSTXT_OG_VERSION', '1.2.1' );
define( 'ROBOTSTXT_OG_PATH', plugin_dir_path( __FILE__ ) ); define( 'ROBOTSTXT_OG_PATH', plugin_dir_path( __FILE__ ) );
define( 'ROBOTSTXT_OG_URL', plugin_dir_url( __FILE__ ) ); define( 'ROBOTSTXT_OG_URL', plugin_dir_url( __FILE__ ) );
define( 'ROBOTSTXT_OG_BASENAME', plugin_basename( __FILE__ ) ); define( 'ROBOTSTXT_OG_BASENAME', plugin_basename( __FILE__ ) );
// Initialize the updater system.
require_once ROBOTSTXT_OG_PATH . 'robotstxt-updater.php';
Robotstxt_Updater::init( __FILE__ );
// Load the main plugin class. // Load the main plugin class.
require_once ROBOTSTXT_OG_PATH . 'includes/class-robotstxt-og-image-fallback.php'; require_once ROBOTSTXT_OG_PATH . 'includes/class-robotstxt-og-image-fallback.php';

View file

@ -1,385 +0,0 @@
<?php
/**
* Generic JSON-based updater for ROBOTSTXT plugins.
*
* This file is designed to be copied to any ROBOTSTXT plugin.
* It auto-configures itself by reading the plugin headers.
*
* @package ROBOTSTXT
* @version 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'Robotstxt_Updater' ) ) {
/**
* Class Robotstxt_Updater
*
* Generic updater that works with any plugin.
* Reads plugin headers and constructs update URL automatically.
*/
class Robotstxt_Updater {
/**
* Plugin file path.
*
* @var string
*/
private string $plugin_file_path;
/**
* Plugin basename (e.g., 'my-plugin/my-plugin.php').
*
* @var string
*/
private string $plugin_basename;
/**
* Plugin slug (directory name).
*
* @var string
*/
private string $plugin_slug;
/**
* Remote JSON URL.
*
* @var string
*/
private string $json_url;
/**
* Cache key.
*
* @var string
*/
private string $cache_key;
/**
* Plugin headers.
*
* @var array<string, mixed>
*/
private array $plugin_data;
/**
* Initialize the updater.
*
* Usage in your main plugin file:
* require_once __DIR__ . '/robotstxt-updater.php';
* Robotstxt_Updater::init( __FILE__ );
*
* @param string $plugin_file_path Absolute path to the main plugin file.
*/
public static function init( string $plugin_file_path ): void {
$instance = new self( $plugin_file_path );
$instance->register();
}
/**
* Constructor.
*
* @param string $plugin_file_path Absolute path to the main plugin file.
*/
private function __construct( string $plugin_file_path ) {
$this->plugin_file_path = $plugin_file_path;
$this->plugin_basename = plugin_basename( $plugin_file_path );
$this->plugin_slug = dirname( $this->plugin_basename );
$this->plugin_data = $this->get_plugin_data();
$this->json_url = $this->build_json_url();
$this->cache_key = 'robotstxt_updater_' . md5( $this->plugin_basename );
}
/**
* Register WordPress hooks.
*/
private function register(): void {
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'inject_update_info' ) );
add_filter( 'plugins_api', array( $this, 'provide_plugin_details' ), 10, 3 );
add_action( 'admin_init', array( $this, 'handle_cache_clear' ) );
add_action( 'robotstxt_updater_clear_cache', array( $this, 'clear_cache' ) );
}
/**
* Get plugin headers.
*
* @return array<string, mixed> Plugin data.
*/
private function get_plugin_data(): array {
if ( ! function_exists( 'get_plugin_data' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
return get_plugin_data( $this->plugin_file_path, false, false );
}
/**
* Build JSON URL from plugin headers.
*
* Tries to use "Gitea Plugin URI" header to construct the URL.
* Falls back to Plugin URI if Gitea URI is not available.
*
* @return string JSON URL.
*/
private function build_json_url(): string {
// Try Gitea Plugin URI (format: "OWNER/REPO" or full URL).
if ( ! empty( $this->plugin_data['Gitea Plugin URI'] ) && is_string( $this->plugin_data['Gitea Plugin URI'] ) ) {
$gitea_uri = $this->plugin_data['Gitea Plugin URI'];
// If it's already a full URL, use it.
if ( str_starts_with( $gitea_uri, 'http' ) ) {
// Extract base URL and construct JSON path.
return rtrim( $gitea_uri, '/' ) . '/raw/branch/main/update.json';
}
// If it's in format "OWNER/REPO", construct full URL.
if ( preg_match( '#^[^/]+/[^/]+$#', $gitea_uri ) ) {
return "https://git.robotstxt.es/{$gitea_uri}/raw/branch/main/update.json";
}
}
// Fallback: try to extract from Plugin URI.
if ( ! empty( $this->plugin_data['PluginURI'] ) && is_string( $this->plugin_data['PluginURI'] ) ) {
$plugin_uri = $this->plugin_data['PluginURI'];
if ( str_contains( $plugin_uri, 'git.robotstxt.es' ) ) {
return rtrim( $plugin_uri, '/' ) . '/raw/branch/main/update.json';
}
}
// Last resort: construct from plugin slug.
return "https://git.robotstxt.es/ROBOTSTXT/{$this->plugin_slug}/raw/branch/main/update.json";
}
/**
* Inject update info into WP's plugin update transient.
*
* @param object|mixed $transient The update_plugins transient.
*
* @return object The modified transient.
*/
public function inject_update_info( $transient ) {
if ( ! ( $transient instanceof stdClass ) ) {
$transient = new stdClass();
}
if ( empty( $transient->checked ) || ! is_array( $transient->checked ) ) {
return $transient;
}
if ( empty( $transient->checked[ $this->plugin_basename ] ) ) {
return $transient;
}
$current_version = $transient->checked[ $this->plugin_basename ];
$remote = $this->get_remote_data();
if ( empty( $remote['version'] ) || empty( $remote['download_url'] ) ) {
return $transient;
}
if ( ! $this->is_compatible( $remote ) ) {
return $transient;
}
if ( is_string( $current_version ) && is_string( $remote['version'] ) && version_compare( $remote['version'], $current_version, '>' ) ) {
$update = (object) array(
'slug' => $remote['slug'] ?? $this->plugin_slug,
'plugin' => $this->plugin_basename,
'new_version' => $remote['version'],
'url' => $remote['homepage'] ?? $this->plugin_data['PluginURI'] ?? '',
'package' => $remote['download_url'],
'tested' => $remote['tested'] ?? '',
'requires' => $remote['requires'] ?? '',
'requires_php' => $remote['requires_php'] ?? '',
);
$transient->response[ $this->plugin_basename ] = $update;
}
return $transient;
}
/**
* Provide "View details" modal content.
*
* @param false|object|array<string, mixed> $result The result object or array.
* @param string $action The type of information being requested.
* @param object $args Plugin API arguments.
*
* @return false|object|array<string, mixed> The plugin information object or false.
*/
public function provide_plugin_details( $result, string $action, object $args ) {
if ( 'plugin_information' !== $action ) {
return $result;
}
if ( empty( $args->slug ) || $args->slug !== $this->plugin_slug ) {
return $result;
}
$remote = $this->get_remote_data();
if ( empty( $remote['version'] ) ) {
return $result;
}
return (object) array(
'name' => $remote['name'] ?? $this->plugin_data['Name'] ?? $this->plugin_slug,
'slug' => $remote['slug'] ?? $this->plugin_slug,
'version' => $remote['version'],
'author' => $remote['author'] ?? $this->plugin_data['Author'] ?? '',
'homepage' => $remote['homepage'] ?? $this->plugin_data['PluginURI'] ?? '',
'requires' => $remote['requires'] ?? '',
'tested' => $remote['tested'] ?? '',
'requires_php' => $remote['requires_php'] ?? '',
'sections' => array(
'description' => $remote['description'] ?? $this->plugin_data['Description'] ?? '',
'changelog' => $remote['changelog'] ?? '',
),
'download_link' => $remote['download_url'] ?? '',
);
}
/**
* Get remote data with caching and HMAC signature verification.
*
* @return array<string, mixed> Remote data.
*/
private function get_remote_data(): array {
$cached = get_site_transient( $this->cache_key );
// Verify HMAC signature if AUTH_SALT is defined and cache has signature.
if ( false !== $cached && defined( 'AUTH_SALT' ) && '' !== AUTH_SALT ) {
if ( is_array( $cached ) && isset( $cached['signature'], $cached['data'] ) ) {
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- HMAC signature over a trusted local array; not deserialization of untrusted input.
$expected_sig = hash_hmac( 'sha256', $this->cache_key . serialize( $cached['data'] ), AUTH_SALT );
if ( is_string( $cached['signature'] ) && hash_equals( $expected_sig, $cached['signature'] ) ) {
// Signature valid, return data.
return is_array( $cached['data'] ) ? $cached['data'] : array();
}
// Signature invalid, delete corrupted cache.
delete_site_transient( $this->cache_key );
$cached = false;
}
}
// If no valid cache, fetch fresh data.
if ( false === $cached ) {
$remote = $this->fetch_json();
// Store with HMAC signature if AUTH_SALT is available.
if ( defined( 'AUTH_SALT' ) && '' !== AUTH_SALT ) {
$payload = array(
'data' => $remote,
'timestamp' => time(),
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- HMAC signature over a trusted local array; not deserialization of untrusted input.
'signature' => hash_hmac( 'sha256', $this->cache_key . serialize( $remote ), AUTH_SALT ),
);
set_site_transient( $this->cache_key, $payload, 6 * HOUR_IN_SECONDS );
} else {
// Fallback to standard caching.
set_site_transient( $this->cache_key, $remote, 6 * HOUR_IN_SECONDS );
}
return $remote;
}
// Legacy cache format without signature (backward compatibility).
return is_array( $cached ) ? $cached : array();
}
/**
* Fetch JSON from remote URL.
*
* @return array<string, mixed> Decoded JSON data.
*/
private function fetch_json(): array {
$response = wp_remote_get(
$this->json_url,
array(
'timeout' => 10,
'headers' => array(
'Accept' => 'application/json',
),
)
);
if ( is_wp_error( $response ) ) {
return array();
}
$code = (int) wp_remote_retrieve_response_code( $response );
if ( $code < 200 || $code >= 300 ) {
return array();
}
$body = wp_remote_retrieve_body( $response );
$data = json_decode( $body, true );
return is_array( $data ) ? $data : array();
}
/**
* Check compatibility.
*
* @param array<string, mixed> $remote Remote data.
*
* @return bool True if compatible.
*/
private function is_compatible( array $remote ): bool {
if ( ! empty( $remote['requires_php'] ) && is_string( $remote['requires_php'] ) ) {
if ( version_compare( PHP_VERSION, $remote['requires_php'], '<' ) ) {
return false;
}
}
if ( ! empty( $remote['requires'] ) && is_string( $remote['requires'] ) ) {
if ( version_compare( get_bloginfo( 'version' ), $remote['requires'], '<' ) ) {
return false;
}
}
return true;
}
/**
* Handle manual cache clear via URL parameter.
*/
public function handle_cache_clear(): void {
// Check if this is a cache clear request first.
$clear_cache = filter_input( INPUT_GET, 'robotstxt_clear_update_cache', FILTER_UNSAFE_RAW );
if ( null === $clear_cache ) {
return;
}
// This is a cache clear request - now verify nonce.
$nonce_raw = filter_input( INPUT_GET, '_wpnonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$nonce = $nonce_raw ? sanitize_text_field( wp_unslash( $nonce_raw ) ) : '';
if ( ! wp_verify_nonce( $nonce, 'robotstxt_clear_update_cache' ) ) {
wp_die( esc_html__( 'Security check failed', 'robotstxt-og' ) );
}
// Check permissions.
if ( ! current_user_can( 'update_plugins' ) ) {
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-og' ) );
}
$this->clear_cache();
wp_safe_redirect( remove_query_arg( array( 'robotstxt_clear_update_cache', '_wpnonce' ) ) );
exit;
}
/**
* Clear update cache.
*/
public function clear_cache(): void {
delete_site_transient( $this->cache_key );
delete_site_transient( 'update_plugins' );
}
}
}

View file

@ -5,7 +5,7 @@
* Fired when the plugin is uninstalled. * Fired when the plugin is uninstalled.
* *
* @package ROBOTSTXT_OG * @package ROBOTSTXT_OG
* @version 1.0.0 * @version 1.2.1
*/ */
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) { if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
@ -79,6 +79,14 @@ function robotstxt_og_uninstall_cleanup(): void {
// Clear related transients. // Clear related transients.
delete_transient( 'robotstxt_og_diagnostics_cache' ); delete_transient( 'robotstxt_og_diagnostics_cache' );
// Clear the legacy self-updater cache (updater removed in 1.2.1). Site
// transients are network-wide on Multisite, so one call covers all sites.
delete_site_transient( 'robotstxt_updater_' . md5( 'robotstxt-og/robotstxt-og.php' ) );
// Delete the per-user Manager notice dismissal flag. User meta is
// network-global on Multisite, so one call covers all users and sites.
delete_metadata( 'user', 0, '_robotstxt_og_manager_notice_dismissed', '', true );
// On Multisite, clean up site-by-site. // On Multisite, clean up site-by-site.
if ( is_multisite() ) { if ( is_multisite() ) {
$sites = get_sites( $sites = get_sites(

View file

@ -1,8 +1,8 @@
{ {
"name": "OpenGraph (by ROBOTSTXT)", "name": "OpenGraph (by ROBOTSTXT)",
"slug": "robotstxt-og", "slug": "robotstxt-og",
"version": "1.2.0", "version": "1.2.1",
"download_url": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-og/releases/download/1.2.0/robotstxt-og-1.2.0.zip", "download_url": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-og/releases/download/1.2.1/robotstxt-og-1.2.1.zip",
"requires": "4.9.8", "requires": "4.9.8",
"requires_php": "8.0", "requires_php": "8.0",
"tested": "7.0", "tested": "7.0",
@ -11,10 +11,10 @@
"author_profile": "https://www.robotstxt.es/", "author_profile": "https://www.robotstxt.es/",
"homepage": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-og", "homepage": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-og",
"description": "Intelligent Open Graph meta tags with automatic image fallback from AVIF/WebP to JPEG/PNG for social media crawlers. Outputs a complete set of og:* and twitter:* meta tags, detects incompatible image formats, and resolves JPEG/PNG alternatives via HTTP HEAD requests.", "description": "Intelligent Open Graph meta tags with automatic image fallback from AVIF/WebP to JPEG/PNG for social media crawlers. Outputs a complete set of og:* and twitter:* meta tags, detects incompatible image formats, and resolves JPEG/PNG alternatives via HTTP HEAD requests.",
"changelog": "<h3>1.2.0 &#8212; 2026-08-10</h3><ul><li><strong>Added:</strong> Per-term Open Graph metadata for Categories and Tags (term edit page) &#8212; custom OG title, image, and description, exposed on term archives.</li><li><strong>Added:</strong> Manual og:image:alt override (per-post/term), emitted as og:image:alt and twitter:image:alt.</li><li><strong>Added:</strong> Per-post og:type override (Default / website / article); article:* sub-tags now emit on the resolved article type.</li><li><strong>Added:</strong> Twitter/X twitter:url (canonical) and per-post twitter:creator (author handle).</li><li><strong>Added:</strong> Platform integration tags (global settings) &#8212; Facebook fb:app_id/admins/pages, Pinterest p:domain_verify + disable-pinning toggle, Telegram telegram:channel, Slack slack-app-id.</li><li><strong>Added:</strong> Mobile / Web App tags (global settings) &#8212; theme-color, web-app name, PWA standalone toggle, iOS status bar style, format-detection; Open Graph namespace added to &lt;html&gt;.</li></ul><h3>1.1.0 &#8212; 2026-03-28</h3><ul><li><strong>Fixed:</strong> Fatal TypeError in handle_thumbnail_change(): the deleted_post_meta action passes an array of meta IDs as its first argument. The method signature now accepts int|array to handle both updated_post_meta and deleted_post_meta correctly.</li><li><strong>Security:</strong> Added SSRF protection to all outbound HTTP HEAD requests via is_safe_url() &#8212; private and reserved IP ranges are now blocked.</li><li><strong>Security:</strong> Replaced deprecated FILTER_SANITIZE_SPECIAL_CHARS with FILTER_SANITIZE_FULL_SPECIAL_CHARS throughout.</li><li><strong>Added:</strong> GDPR Privacy API support &#8212; custom OG title and description post meta are now included in WordPress personal data export and erase requests.</li><li><strong>Changed:</strong> Settings page and REST API now require edit_others_posts capability, allowing editors to manage OG settings and refresh fallback images.</li><li><strong>Changed:</strong> Minimum supported WordPress version raised to 6.8. Network: true header added.</li><li><strong>Added:</strong> PHPUnit test suite with Brain\\Monkey; covers SSRF logic, image format detection, and cache clearing.</li></ul><h3>1.0.0 &#8212; 2026-02-18</h3><ul><li><strong>Initial Release:</strong> First stable release of OpenGraph (by ROBOTSTXT).</li></ul>", "changelog": "",
"sections": { "sections": {
"description": "<p>Social media crawlers &#8212; Facebook, Twitter/X, LinkedIn, WhatsApp &#8212; do not support modern image formats such as AVIF and WebP. When a post&#8217;s featured image uses one of these formats, the platform shows a broken or missing image preview.</p><p><strong>OpenGraph (by ROBOTSTXT)</strong> outputs a complete set of Open Graph and Twitter Card meta tags and solves this automatically. It detects whether each post&#8217;s featured image is in a supported format and, when needed, resolves and caches a compatible JPEG or PNG alternative.</p><ul><li>Full og:* and twitter:* meta tag output</li><li>Automatic AVIF/WebP/GIF/BMP/SVG/TIFF detection with JPEG/PNG fallback</li><li>Per-post OG title and description overrides via post editor meta box</li><li>Global fallback image URL and homepage-specific image URL</li><li>Yoast SEO and RankMath integration (no duplicate tags)</li><li>Postmeta caching with automatic invalidation</li><li>Admin panel with Settings, Tools, and Diagnostics tabs</li><li>WP-CLI commands and REST API endpoints</li><li>Multisite compatible</li></ul>", "description": "<p>Social media crawlers &#8212; Facebook, Twitter/X, LinkedIn, WhatsApp &#8212; do not support modern image formats such as AVIF and WebP. When a post&#8217;s featured image uses one of these formats, the platform shows a broken or missing image preview.</p><p><strong>OpenGraph (by ROBOTSTXT)</strong> outputs a complete set of Open Graph and Twitter Card meta tags and solves this automatically. It detects whether each post&#8217;s featured image is in a supported format and, when needed, resolves and caches a compatible JPEG or PNG alternative.</p><ul><li>Full og:* and twitter:* meta tag output</li><li>Automatic AVIF/WebP/GIF/BMP/SVG/TIFF detection with JPEG/PNG fallback</li><li>Per-post OG title and description overrides via post editor meta box</li><li>Global fallback image URL and homepage-specific image URL</li><li>Yoast SEO and RankMath integration (no duplicate tags)</li><li>Postmeta caching with automatic invalidation</li><li>Admin panel with Settings, Tools, and Diagnostics tabs</li><li>WP-CLI commands and REST API endpoints</li><li>Multisite compatible</li></ul>",
"changelog": "<h3>1.2.0 &#8212; 2026-08-10</h3><ul><li><strong>Added:</strong> Per-term Open Graph metadata for Categories and Tags (term edit page) &#8212; custom OG title, image, and description, exposed on term archives.</li><li><strong>Added:</strong> Manual og:image:alt override (per-post/term), emitted as og:image:alt and twitter:image:alt.</li><li><strong>Added:</strong> Per-post og:type override (Default / website / article); article:* sub-tags now emit on the resolved article type.</li><li><strong>Added:</strong> Twitter/X twitter:url (canonical) and per-post twitter:creator (author handle).</li><li><strong>Added:</strong> Platform integration tags (global settings) &#8212; Facebook fb:app_id/admins/pages, Pinterest p:domain_verify + disable-pinning toggle, Telegram telegram:channel, Slack slack-app-id.</li><li><strong>Added:</strong> Mobile / Web App tags (global settings) &#8212; theme-color, web-app name, PWA standalone toggle, iOS status bar style, format-detection; Open Graph namespace added to &lt;html&gt;.</li></ul><h3>1.1.0 &#8212; 2026-03-28</h3><ul><li><strong>Fixed:</strong> Fatal TypeError in handle_thumbnail_change(): the deleted_post_meta action passes an array of meta IDs as its first argument. The method signature now accepts int|array to handle both updated_post_meta and deleted_post_meta correctly.</li><li><strong>Security:</strong> Added SSRF protection to all outbound HTTP HEAD requests via is_safe_url() &#8212; private and reserved IP ranges are now blocked.</li><li><strong>Security:</strong> Replaced deprecated FILTER_SANITIZE_SPECIAL_CHARS with FILTER_SANITIZE_FULL_SPECIAL_CHARS throughout.</li><li><strong>Added:</strong> GDPR Privacy API support &#8212; custom OG title and description post meta are now included in WordPress personal data export and erase requests.</li><li><strong>Changed:</strong> Settings page and REST API now require edit_others_posts capability, allowing editors to manage OG settings and refresh fallback images.</li><li><strong>Changed:</strong> Minimum supported WordPress version raised to 6.8. Network: true header added.</li><li><strong>Added:</strong> PHPUnit test suite with Brain\\Monkey; covers SSRF logic, image format detection, and cache clearing.</li></ul><h3>1.0.0 &#8212; 2026-02-18</h3><ul><li><strong>Initial Release:</strong> First stable release of OpenGraph (by ROBOTSTXT).</li></ul>" "changelog": ""
}, },
"banners": { "banners": {
"low": "", "low": "",