This commit is contained in:
Javier Casares 2026-02-19 11:15:55 +00:00
commit f7702f2872
25 changed files with 6453 additions and 0 deletions

240
docs/FILTERS-HOOKS.md Normal file
View file

@ -0,0 +1,240 @@
# Filters & Hooks Reference
Developer reference for all filters and actions provided by the **OpenGraph (by ROBOTSTXT)** plugin.
## Table of Contents
- [Filters](#filters)
- [robotstxt_og_external_image_enabled](#robotstxt_og_external_image_enabled)
- [robotstxt_og_external_image_timeout](#robotstxt_og_external_image_timeout)
- [robotstxt_og_taxonomy_image](#robotstxt_og_taxonomy_image)
- [robotstxt_og_enable_logging](#robotstxt_og_enable_logging)
- [Actions](#actions)
- [SEO Plugin Integrations](#seo-plugin-integrations)
---
## Filters
### `robotstxt_og_external_image_enabled`
Controls whether the plugin attempts to resolve fallback images for external URLs (images hosted on a different domain than the WordPress site).
**Default:** `true`
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `$enabled` | `bool` | Whether external image resolution is enabled. |
| `$image_url` | `string` | The external image URL being evaluated. |
**Returns:** `bool`
**Example — disable external image resolution entirely:**
```php
add_filter( 'robotstxt_og_external_image_enabled', '__return_false' );
```
**Example — disable only for a specific CDN domain:**
```php
add_filter( 'robotstxt_og_external_image_enabled', function ( bool $enabled, string $image_url ): bool {
if ( str_contains( $image_url, 'cdn.example.com' ) ) {
return false;
}
return $enabled;
}, 10, 2 );
```
---
### `robotstxt_og_external_image_timeout`
Sets the HTTP request timeout (in seconds) used when verifying whether a fallback image URL exists via a HEAD request.
**Default:** `5` (seconds)
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `$timeout` | `int` | Timeout in seconds for the HEAD request. |
| `$url` | `string` | The image URL being tested. |
**Returns:** `int`
**Example — increase timeout for slow external servers:**
```php
add_filter( 'robotstxt_og_external_image_timeout', function ( int $timeout, string $url ): int {
if ( str_contains( $url, 'slow-cdn.example.com' ) ) {
return 15;
}
return $timeout;
}, 10, 2 );
```
**Example — set a global lower timeout for performance:**
```php
add_filter( 'robotstxt_og_external_image_timeout', function (): int {
return 3;
} );
```
---
### `robotstxt_og_taxonomy_image`
Provides a fallback OG image URL for taxonomy archive pages (categories, tags, custom taxonomies). By default, taxonomy archives do not have a featured image, so this filter is the primary way to supply one.
**Default:** `''` (empty string — no image)
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `$image_url` | `string` | Image URL to use. Empty string by default. |
| `$term_id` | `int` | The term ID of the current taxonomy archive. |
**Returns:** `string` A valid image URL, or empty string to skip.
**Example — use a custom field set on the term:**
```php
add_filter( 'robotstxt_og_taxonomy_image', function ( string $image_url, int $term_id ): string {
$custom_image_id = get_term_meta( $term_id, 'og_image_id', true );
if ( $custom_image_id ) {
$url = wp_get_attachment_url( (int) $custom_image_id );
return $url ? $url : $image_url;
}
return $image_url;
}, 10, 2 );
```
**Example — use a WooCommerce category thumbnail:**
```php
add_filter( 'robotstxt_og_taxonomy_image', function ( string $image_url, int $term_id ): string {
$thumbnail_id = get_term_meta( $term_id, 'thumbnail_id', true );
if ( $thumbnail_id ) {
$url = wp_get_attachment_url( (int) $thumbnail_id );
return $url ? $url : $image_url;
}
return $image_url;
}, 10, 2 );
```
---
### `robotstxt_og_enable_logging`
Enables or disables debug logging to `wp-content/debug.log`. When enabled, resolution events (cache hits, cache misses, format detection, HEAD request results) are written to the error log.
**Default:** `false`
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `$enabled` | `bool` | Whether debug logging is active. |
**Returns:** `bool`
**Note:** Requires `WP_DEBUG` and `WP_DEBUG_LOG` to be enabled in `wp-config.php` for output to appear in `debug.log`.
**Example — enable logging (e.g. during development, in `wp-config.php`):**
```php
// wp-config.php
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
```
```php
// functions.php or a mu-plugin
add_filter( 'robotstxt_og_enable_logging', '__return_true' );
```
**Example — enable logging only for specific users:**
```php
add_filter( 'robotstxt_og_enable_logging', function ( bool $enabled ): bool {
return current_user_can( 'manage_options' ) ? true : $enabled;
} );
```
---
## Actions
The plugin does not currently expose custom action hooks. WordPress core hooks used internally include:
| Hook | Context | Purpose |
|------|---------|---------|
| `plugins_loaded` | Global | Loads text domain for translations. |
| `wp_head` | Frontend | Injects `og:image` meta tags (only when no SEO plugin is active). |
| `admin_menu` | Admin | Registers the Settings > OpenGraph settings page. |
| `admin_init` | Admin | Registers settings, handles cache clear/resolve actions. |
| `admin_enqueue_scripts` | Admin | Enqueues media uploader and admin CSS/JS. |
| `rest_api_init` | REST API | Registers the `robotstxt-og/v1` REST endpoints. |
| `updated_post_meta` | Global | Auto-clears fallback cache when `_thumbnail_id` changes. |
| `deleted_post_meta` | Global | Auto-clears fallback cache when `_thumbnail_id` is removed. |
---
## SEO Plugin Integrations
When a supported SEO plugin is detected, the plugin switches from direct `og:image` tag injection to filtering the SEO plugin's output. This prevents duplicate meta tags.
### Yoast SEO
**Filter:** `wpseo_opengraph_image`
When Yoast SEO is active (`WPSEO_VERSION` is defined), the plugin hooks into this filter to provide the resolved fallback image. The plugin only overrides the value if a valid fallback URL is resolved; otherwise it returns the original Yoast value unchanged.
### RankMath
**Filter:** `rank_math/opengraph/facebook/og_image`
When RankMath is active (`RankMath` class exists), the plugin hooks into this filter with the same logic as the Yoast integration.
### Adding Support for Other SEO Plugins
To integrate with another SEO plugin, hook into the plugin's OG image filter and call the resolver manually:
```php
add_filter( 'your_seo_plugin_og_image_filter', function ( string $image ) : string {
if ( ! is_singular() ) {
return $image;
}
$post_id = get_queried_object_id();
$resolver = Robotstxt_OG_Image_Fallback::get_instance()->get_resolver();
$fallback = $resolver->get_fallback_image( $post_id );
return ! empty( $fallback ) ? $fallback : $image;
} );
```
---
## Postmeta Keys (Internal Cache)
These postmeta keys are used internally for caching and should not be modified directly:
| Meta Key | Type | Description |
|----------|------|-------------|
| `_og_image_fallback_url` | `string` | Cached resolved fallback image URL for a post. |
These transient keys are used for negative caching (failed HEAD request results):
| Transient Key Pattern | TTL | Description |
|-----------------------|-----|-------------|
| `robotstxt_og_miss_{md5_of_url}` | 1 hour | Marks a URL as unreachable to prevent repeated requests. |

View file

@ -0,0 +1,183 @@
# Open Graph & Twitter Cards — Reference
This document covers the Open Graph (OG) and Twitter Cards meta tag specifications relevant to this plugin, including which tags are mandatory, recommended, optional, and which this plugin outputs automatically.
---
## Open Graph Protocol
Defined by Facebook/Meta. All OG tags use the `property` attribute.
### Core Tags (og: namespace)
| Property | Type | Required | Notes |
|---|---|---|---|
| `og:title` | string | **Required** | Title of the content. Used by all social crawlers. |
| `og:type` | string | **Required** | Content type: `website`, `article`, `video.movie`, etc. |
| `og:url` | URL | **Required** | Canonical URL of the page. |
| `og:description` | string | Recommended | Short description (24 sentences). Max ~300 chars. |
| `og:site_name` | string | Recommended | Name of the overall site (e.g. "My Blog"). |
| `og:locale` | string | Recommended | Locale in `language_TERRITORY` format (e.g. `es_ES`). |
| `og:image` | URL | **Required** (for cards) | Must be JPEG or PNG for social crawler compatibility. Min 200×200 px. Recommended 1200×630 px. |
| `og:image:secure_url` | URL | Optional | HTTPS version of `og:image`. Same value when the site is HTTPS-only. |
| `og:image:type` | MIME type | Recommended | MIME type of the image (`image/jpeg`, `image/png`). |
| `og:image:width` | integer | Recommended | Width in pixels. Avoids reflow in crawler previews. |
| `og:image:height` | integer | Recommended | Height in pixels. |
| `og:image:alt` | string | Recommended | Alt text for the image. Required for accessibility audits. |
### Article Tags (article: namespace)
Used when `og:type = article`. All are optional but recommended for news/blog content.
| Property | Type | Notes |
|---|---|---|
| `article:published_time` | ISO 8601 datetime | Publication date (`c` format in PHP: `get_the_date('c')`). |
| `article:modified_time` | ISO 8601 datetime | Last modification date. |
| `article:author` | URL | Profile page of the author (Facebook profile URL). Often omitted. |
| `article:section` | string | Primary category or section (e.g. "Technology"). |
| `article:tag` | string | Topic tags. Can be repeated once per tag. |
| `article:expiration_time` | ISO 8601 datetime | When the article expires (rarely used). |
### Video Tags (video: namespace)
For `og:type = video.movie`, `video.episode`, etc. Out of scope for this plugin.
---
## Twitter Cards
Defined by X (formerly Twitter). Tags use the `name` attribute (not `property`).
Twitter falls back to `og:*` tags if the corresponding `twitter:*` tag is absent — **except** `twitter:card`, which is always required.
### Card Types
| Value | Description |
|---|---|
| `summary` | Small square image (minimum 144×144 px). |
| `summary_large_image` | Large rectangular image (minimum 300×157 px, recommended 1200×628 px). Most common for blog/news content. |
| `app` | Promotes a mobile app. |
| `player` | Embeds a video/audio player. |
### Twitter Tags
| Name | Required | Falls back to | Notes |
|---|---|---|---|
| `twitter:card` | **Required** | — | Must always be present. Without it, no Twitter Card is shown. |
| `twitter:site` | Recommended | — | `@username` of the site's Twitter/X account. |
| `twitter:creator` | Optional | — | `@username` of the content author. |
| `twitter:title` | Recommended | `og:title` | Title of the content. |
| `twitter:description` | Recommended | `og:description` | Description. Max 200 chars. |
| `twitter:image` | Recommended | `og:image` | Must be JPEG, PNG, WebP, or GIF. Max 5 MB. |
| `twitter:image:alt` | Recommended | `og:image:alt` | Alt text for the image. Max 420 chars. |
---
## What This Plugin Outputs
### When no SEO plugin is active (direct injection)
The plugin outputs a **complete** set of OG and Twitter Card tags via `wp_head` (priority 5).
#### Open Graph tags
```html
<!-- Core -->
<meta property="og:title" content="..." />
<meta property="og:type" content="article" /> <!-- or "website" -->
<meta property="og:url" content="..." />
<meta property="og:description" content="..." /> <!-- when available -->
<meta property="og:site_name" content="..." />
<meta property="og:locale" content="es_ES" />
<!-- Image (when a compatible image is resolved) -->
<meta property="og:image" content="https://...jpg" />
<meta property="og:image:secure_url" content="https://...jpg" /> <!-- HTTPS only -->
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:type" content="image/jpeg" />
<meta property="og:image:alt" content="..." /> <!-- when set in media library -->
<!-- Article-specific (og:type = article only) -->
<meta property="article:published_time" content="2026-02-18T00:00:00+00:00" />
<meta property="article:modified_time" content="2026-02-18T00:00:00+00:00" />
<meta property="article:section" content="Technology" /> <!-- primary category -->
<meta property="article:tag" content="WordPress" /> <!-- repeated per tag -->
```
#### Twitter Card tags
```html
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@example" /> <!-- when configured in Settings -->
<meta name="twitter:image" content="https://...jpg" /> <!-- when image available -->
```
> Twitter falls back to `og:title`, `og:description`, and `og:image` automatically, so those tags are not duplicated.
### When Yoast SEO or RankMath is active
The plugin acts as a **corrector only**: it filters the image URL via the SEO plugin's filter hook, converting incompatible formats (AVIF/WebP) to JPEG/PNG. All other OG/Twitter tags are managed by the SEO plugin.
---
## og:type Values Reference
| Value | When to use |
|---|---|
| `website` | Default for homepages and most pages. |
| `article` | Blog posts, news articles. This plugin uses this for `is_singular('post')`. |
| `profile` | User profile pages. |
| `video.movie` | Movie pages. |
| `video.episode` | TV episode pages. |
| `music.song` | Song pages. |
| `music.album` | Album pages. |
---
## Per-Post Overrides (Editor Meta Box)
The plugin adds an **"Open Graph / Social Media"** meta box to all post editors, allowing per-post overrides of:
| Field | OG Tag | Fallback |
|---|---|---|
| Custom Title | `og:title` | Post title (`get_the_title()`) |
| Custom Description | `og:description` | Post excerpt, or empty |
These overrides are stored as post meta:
- `_og_title` — custom OG title
- `_og_description` — custom OG description
---
## Context → og:type Mapping (this plugin)
| WordPress context | `og:type` |
|---|---|
| `is_singular('post')` | `article` |
| `is_singular('page')` | `website` |
| `is_singular(other)` | `website` |
| `is_front_page()` / `is_home()` | `website` |
| `is_tax()` / `is_category()` / `is_tag()` | `website` |
---
## Image Compatibility
Social crawlers (Facebook, X, LinkedIn, WhatsApp, Telegram) generally require:
- Format: **JPEG or PNG** (WebP partial support; AVIF not supported)
- Minimum size: 200×200 px (Facebook requires 200×200 for `summary`)
- Recommended: 1200×630 px for `summary_large_image`
- Max file size: 8 MB (Facebook), 5 MB (Twitter)
This plugin's core function is to detect when a featured image is in an incompatible format (AVIF, WebP) and automatically serve a JPEG/PNG alternative via fallback resolution.
---
## Validation Tools
- **Facebook**: [Sharing Debugger](https://developers.facebook.com/tools/debug/)
- **Twitter/X**: [Card Validator](https://cards-dev.twitter.com/validator)
- **LinkedIn**: [Post Inspector](https://www.linkedin.com/post-inspector/)
- **OpenGraph.xyz**: [OpenGraph preview](https://www.opengraph.xyz/)
- **Metatags.io**: [Meta tag preview](https://metatags.io/)

103
docs/WP-CLI.md Normal file
View file

@ -0,0 +1,103 @@
# WP-CLI Command Reference
The plugin registers WP-CLI commands under the `og-fallback` namespace.
## Commands
### `wp og-fallback resolve`
Resolves (or re-resolves) the OG fallback image for one or all posts with featured images.
**Usage:**
```bash
wp og-fallback resolve [<post_id>] [--all] [--dry-run] [--post-type=<type>]
```
**Arguments:**
| Argument | Description |
|----------|-------------|
| `<post_id>` | (optional) Single post ID to resolve. |
| `--all` | Process all posts that have a featured image. |
| `--dry-run` | Preview without making any changes. |
| `--post-type=<type>` | Limit `--all` to a specific post type (default: `any`). |
**Examples:**
```bash
# Resolve fallback for a single post
wp og-fallback resolve 123
# Preview for a single post without saving
wp og-fallback resolve 123 --dry-run
# Re-resolve all posts (clears cache first)
wp og-fallback resolve --all
# Re-resolve only 'product' post type posts
wp og-fallback resolve --all --post-type=product
# Dry-run all (shows count, no changes)
wp og-fallback resolve --all --dry-run
```
**Output examples:**
```
Success: Post 123 resolved to https://example.com/uploads/image.jpg
Warning: Post 456: no compatible image found.
Success: Resolved 47 posts. Failed: 2.
Found 49 posts with featured images. (dry-run, no changes made)
```
---
### `wp og-fallback clear-cache`
Deletes cached fallback URLs from postmeta.
**Usage:**
```bash
wp og-fallback clear-cache [<post_id>] [--all] [--dry-run]
```
**Arguments:**
| Argument | Description |
|----------|-------------|
| `<post_id>` | (optional) Single post ID to clear cache for. |
| `--all` | Clear all cached fallback URLs. |
| `--dry-run` | Preview without making any changes. |
**Examples:**
```bash
# Clear cache for a single post
wp og-fallback clear-cache 123
# Clear all cached fallback URLs
wp og-fallback clear-cache --all
# Preview how many entries would be cleared
wp og-fallback clear-cache --all --dry-run
```
**Output examples:**
```
Success: Cleared cached fallback URL for post 123.
Warning: Post 456 has no cached fallback URL.
Success: Cleared 47 cached fallback URLs.
Found 47 cached fallback URLs. (dry-run, no changes made)
```
---
## Notes
- Both commands require WP-CLI 2.x.
- No capability check is enforced at the CLI level (WP-CLI access implies server-level trust).
- The `--all` flag with `resolve` is equivalent to clicking **Re-resolve All Images** in the admin Tools tab.
- The `--all` flag with `clear-cache` is equivalent to clicking **Clear All Cached URLs** in the admin Tools tab.