=== MarkExcell Meta Tags and Structured Data ===
Contributors:      markwexcell
Tags:              seo, meta, open graph, schema, json-ld, structured data, twitter card
Requires at least: 6.0
Tested up to:      7.0
Requires PHP:      7.4
Stable tag:        1.0.0
License:           GPL-2.0-or-later
License URI:       https://www.gnu.org/licenses/gpl-2.0.html

Per-page SEO titles, meta descriptions, Open Graph tags, Twitter Cards, and JSON-LD structured data — no bloat, no settings pages.

== Description ==

**MarkExcell Meta Tags and Structured Data** is a lightweight plugin for WordPress sites that need precise control over their on-page SEO metadata without installing a full SEO suite.

Every feature is controlled from a pair of meta boxes that appear directly on the page or post edit screen, keeping everything in context.

**What it does:**

* Adds a custom **title tag** per page or post, with a fallback to the WordPress default (post title + site name).
* Adds a custom **meta description**, falling back to the post excerpt, then the site-wide tagline.
* Outputs **Open Graph tags** (`og:title`, `og:description`, `og:image`, `og:url`, `og:type`, `og:site_name`) with per-page overrides.
* Outputs **Twitter Card** (`summary_large_image`) tags using the same data.
* Provides a freeform **JSON-LD structured data** field for any page or post, with JSON validation on save and a clear error message if the JSON is malformed.
* **Auto-generates a `BlogPosting` schema** for posts when no manual JSON-LD is provided, referencing your site's `#person` and `#website` entities.

**What it does NOT do:**

* No settings pages, no dashboard widgets, no telemetry.
* No sitemap generation.
* No keyword analysis or content scoring.
* No redirection management.

**Important:** Do not run this plugin alongside a full SEO plugin (Yoast SEO, Rank Math, All in One SEO, etc.) — you will get duplicate title tags and meta tags in your `<head>`.

**For developers:**

Three filters let you customise the site-wide defaults without editing the plugin:

* `me_seo_default_description` — override the fallback meta description.
* `me_seo_default_og_image` — set a site-wide default OG image URL.
* `me_seo_post_types` — add additional post types to show the meta boxes on (defaults to `page` and `post`).

Example:

    add_filter( 'me_seo_default_description', function() {
        return 'Your custom site-wide description here.';
    } );

    add_filter( 'me_seo_default_og_image', function() {
        return 'https://example.com/wp-content/uploads/default-share.png';
    } );

    // Add meta boxes to a custom post type called 'project'
    add_filter( 'me_seo_post_types', function( $types ) {
        $types[] = 'project';
        return $types;
    } );

== Installation ==

1. Upload the `markexcell-meta-tags-structured-data` folder to `/wp-content/plugins/`.
2. Activate the plugin from **Plugins → Installed Plugins** in your WordPress admin.
3. Open any page or post for editing — two new meta boxes will appear:
   * **SEO: Title, Description & Open Graph** (near the top of the edit screen)
   * **Structured Data (JSON-LD)** (below the main editor)
4. Fill in whichever fields you need, or leave them empty to use the automatic fallbacks.
5. Publish or update the post/page as normal.

There is no settings page to configure.

== Usage ==

= SEO: Title, Description & Open Graph meta box =

**Title tag**
Enter the exact string you want to appear in the browser tab and search results. Leave blank and WordPress will use its default format: *Post Title – Site Name*.

Recommended length: 60 characters or fewer.

**Meta description**
The sentence or two that appears beneath your page title in search results. Leave blank to fall back to the post excerpt; if there is no excerpt, the site-wide default description is used.

Recommended length: 120–160 characters.

**Open Graph overrides**
These three fields let you set different text and image for social sharing without changing the SEO fields above — useful when the ideal tweet wording differs from the ideal title tag.

* `og:title` — leave blank to inherit the title tag value.
* `og:description` — leave blank to inherit the meta description.
* `og:image URL` — paste the full URL of a 1200×630 px image. Leave blank to use the post's featured image; if there is no featured image, the site-wide default OG image is used (set via the `me_seo_default_og_image` filter).

= Structured Data (JSON-LD) meta box =

Paste a raw JSON-LD object into this field. The plugin will:

1. Validate that the JSON is well-formed.
2. Check that a top-level `@context` or `@graph` key is present.
3. Re-encode and store the canonical form on save.
4. Output it as a `<script type="application/ld+json">` block in `<head>` on the front end.

If the JSON fails validation, an error message is shown at the top of the meta box and the **previous valid value is kept** — your live schema is never broken by a typo.

Do **not** include the surrounding `<script>` tags — paste the JSON object only.

For posts where this field is left empty, the plugin automatically generates a `BlogPosting` schema using the post title, description, published/modified dates, featured image, and author/publisher references pointing to your site's `#person` and `#website` entities.

= Fallback chain summary =

| Field | Primary | Secondary | Tertiary |
|---|---|---|---|
| Title tag | SEO title field | WordPress default (title + site name) | — |
| Meta description | Description field | Post excerpt | Site tagline / filter default |
| og:title | OG title field | SEO title field | WordPress default title |
| og:description | OG description field | Meta description (resolved above) | — |
| og:image | OG image URL field | Featured image | `me_seo_default_og_image` filter |

== Frequently Asked Questions ==

= Will this conflict with Yoast SEO / Rank Math / All in One SEO? =

Yes — running any two SEO plugins that output title tags and Open Graph meta simultaneously will produce duplicate tags. Deactivate your existing SEO plugin before using this plugin, or vice versa.

= The JSON-LD field says "Last save rejected" — what do I do? =

The JSON you pasted is either malformed or is missing a top-level `@context` or `@graph` key. Copy your JSON into a validator such as [validator.schema.org](https://validator.schema.org) or [jsonlint.com](https://jsonlint.com) to find the error, correct it, and save again. Your previously saved schema remains live until a valid replacement is submitted.

= How do I set a site-wide default OG image? =

Use the `me_seo_default_og_image` filter in your theme's `functions.php` or a small mu-plugin:

    add_filter( 'me_seo_default_og_image', function() {
        return 'https://example.com/path/to/default-share-image.png';
    } );

= The auto-generated BlogPosting schema references `#person` and `#website` — where do I define those? =

You define them on your homepage (or a suitable page) using the Structured Data (JSON-LD) meta box. Paste a JSON-LD `@graph` that includes your `Person` and `WebSite` entities with the matching `@id` values. The plugin references them by ID; it does not duplicate the full entity on every post.

= Can I add the meta boxes to custom post types? =

Yes, using the `me_seo_post_types` filter:

    add_filter( 'me_seo_post_types', function( $types ) {
        $types[] = 'your_custom_post_type';
        return $types;
    } );

= Does the plugin need `add_theme_support( 'title-tag' )` to work? =

Yes. The title tag override relies on WordPress managing the `<title>` element. Most modern themes already declare this support. If your theme hard-codes a `<title>` element in `header.php`, the custom title field will have no effect until that is removed.

== Screenshots ==

1. The **SEO: Title, Description & Open Graph** meta box on the post edit screen.
2. The **Structured Data (JSON-LD)** meta box showing a validation error after a malformed paste.

== Changelog ==

= 1.0.0 =
* Initial release.

== Upgrade Notice ==

= 1.0.0 =
Initial release — no upgrade steps required.
