=== One Two Three Post ===
Contributors: onetwothreesend
Tags: social media, facebook, scheduler, post, automation
Requires at least: 6.0
Tested up to: 6.9
Requires PHP: 7.4
Stable tag: 1.6.2
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Schedule and publish Facebook Page posts from WordPress. Calendar + list views, coloured-background text posts, optional first comment.

== Description ==

One Two Three Post is a WordPress plugin for site owners who manage one or more Facebook Pages and want a clean composer + scheduler inside WordPress instead of switching to Meta's tools.

The plugin is fully functional on its own. A separate companion plugin (One Two Three Post Pro, distributed independently) will add AI-generated post drafting and Publer-style multi-network support — One Two Three Post does not require it.

= What's included =

* **Composer** — pick a Page, write the message, attach an image from the WP media library, choose post type, send now or schedule.
* **Native coloured-background text posts** — the Facebook-rendered "text on coloured background" format. 41 background presets ported from a known-working list, plus a custom-preset-ID field for Pages with brand-specific palettes.
* **First comment** — optional comment posted automatically on the published post (handy for newsletter CTAs, link drops, follow-up prompts). A comment failure does not roll back the main post.
* **Scheduling** — pick any future time (Facebook requires ≥10 minutes lead). A WP-Cron worker fires every 5 minutes and pushes due posts to the Graph API. Failed posts retry up to 3 times with the error visible.
* **Calendar view** — month grid showing every scheduled / drafted / sent / failed post. Click a day to filter the list. Counts + status-coloured pills per day.
* **List view** — chronological table with the next post going out at the top. Visual preview tile per row (image thumb for photo posts, dark tile with text excerpt for coloured-background, grey card for text-only).
* **Edit-in-composer** — clicking Edit on any saved post returns to the composer with all fields hydrated (page, message, image, schedule time, type, background, first comment).
* **Encrypted credentials** — your Facebook App Secret and per-Page access tokens are encrypted at rest with AES-256-CBC, keyed off your WordPress authentication keys.

= External services =

This plugin contacts the following Facebook endpoints using credentials you provide. Nothing is sent to any third party without you connecting first.

* **Facebook Login (`https://www.facebook.com/v19.0/dialog/oauth`)** — the OAuth dialog the user is redirected to when clicking "Connect to Facebook" in Settings. Standard Facebook Login flow.
* **Facebook Graph API (`https://graph.facebook.com/v19.0`)** — for the OAuth code-to-token exchange, listing the user's Pages, fetching Page metadata, publishing posts and comments, scheduling posts. Called only after the user has connected a Page and only when posting / scheduling.

The plugin itself does not phone home to any other server. No analytics, no telemetry, no third-party trackers, no opt-in newsletter signups, no remote update channel.

* Facebook Platform Terms: https://developers.facebook.com/terms/
* Facebook Privacy Policy: https://www.facebook.com/privacy/policy/

= Required Facebook permissions =

The plugin requests three Facebook scopes during the Connect flow:

* `pages_manage_posts` — create posts on your Pages
* `pages_read_engagement` — read your Page metadata
* `pages_show_list` — list the Pages you manage

= Useful links =

* [onetwothreesend.com](https://onetwothreesend.com/) — author site, documentation, and support.
* [Privacy policy](https://onetwothreesend.com/privacy/).

== Installation ==

1. Upload the plugin zip via Plugins → Add New → Upload Plugin, or place the folder in `/wp-content/plugins/one-two-three-post/`.
2. Activate the plugin.
3. Create a Facebook App at https://developers.facebook.com/apps. Pick the "Business" use case. Add the "Facebook Login" product.
4. Open **One Two Three Post → Settings**. Copy the Redirect URI shown on that page into your Facebook App's *Facebook Login → Settings → Valid OAuth Redirect URIs* list.
5. Paste your App ID and App Secret into the Settings page and save.
6. Click **Connect to Facebook**, grant the permissions, you are returned to WordPress with your Pages listed.
7. Open **One Two Three Post → New / Compose** and write your first post.

== Frequently Asked Questions ==

= I don't have a Facebook App. Where do I get one? =

At https://developers.facebook.com/apps. Click "Create App", pick "Business", give it a name. Add the "Facebook Login" product. In Facebook Login → Settings, paste the Redirect URI shown in the plugin's Settings page into "Valid OAuth Redirect URIs". You'll then have an App ID and App Secret to copy into the plugin.

= Why does the connect flow ask for `pages_manage_posts`? =

Because that's the Facebook scope required to create a post on a Page. Without it, the plugin can't publish.

= Why does my scheduled post need to be at least 10 minutes in the future? =

Facebook's API requirement, not ours. Facebook rejects scheduled posts whose time is less than 10 minutes from now and more than six months out.

= Where are my Page access tokens stored? =

In the WordPress options table, encrypted at rest with AES-256-CBC using a key derived from your `SECURE_AUTH_KEY` + `SECURE_AUTH_SALT`. They are never sent anywhere except the Facebook Graph API endpoint above.

= My scheduled post didn't fire — what happened? =

Open **One Two Three Post → Scheduled** (or All Posts). Posts that failed have a "Failed" status with the error message visible. Common causes: page access token revoked (reconnect from Settings), image attachment deleted from media library, network error. The plugin retries up to 3 times.

= Does this plugin work with Facebook Groups, Instagram, or my personal Facebook profile? =

No. Pages only. Facebook deprecated programmatic posting to personal profiles years ago, and Group / Instagram posting requires different scopes and permissions that aren't a fit for this plugin.

= What is a "coloured-background text post"? =

The native Facebook short-text-on-a-coloured-background post format — same one Facebook offers in its own composer for short messages. The plugin renders these via Facebook's `text_format_preset_id` parameter (POST to `/{page_id}/feed`). Messages are capped at 130 characters by Facebook.

= What does "first comment" do? =

If you fill the optional First Comment field, the plugin posts that text as a comment on the new post immediately after it goes live. Useful for newsletter CTAs, link drops, or follow-up prompts where Facebook tends to suppress reach when the link is in the main post body.

= What happens to my data if I delete the plugin? =

Deactivating leaves all data in place. Deleting the plugin (Plugins → Delete) removes the plugin's options, scheduled-post entries, and the WP-Cron event via the bundled `uninstall.php`.

== Screenshots ==

1. Composer — pick a Page, write a message, attach an image, choose post type, send or schedule.
2. Coloured-background picker — 41 native Facebook background presets with previews, plus a custom preset-ID field.
3. Calendar view — month grid with post counts per day, status-coloured pills, click a day to filter.
4. List view — next post going out first, then drafts / past activity. Visual preview thumbnail per row.
5. Settings — Facebook App credentials, Connect to Facebook button, list of connected Pages.

== Changelog ==

= 1.6.2 =
* **Fix:** decode HTML entities (`&#8217;`, `&#8212;`, `&#8230;`, `&amp;` etc.) before sending the message text to Facebook. Curly apostrophes and other punctuation entities were being published to Facebook as literal `&#8217;` strings instead of the intended characters. Applies to both the parent-post message and the first-comment message.
* **Fix:** add the `pages_manage_engagement` scope to the Facebook OAuth flow. Without it, the page access token can't post comments on the page's own posts and Facebook returns `(#200) You do not have sufficient permissions` when the first-comment feature fires. Sites that authorized before this release need to reconnect their Facebook page (Settings → Connect to Facebook) to grant the new scope.

= 1.6.1 =
* **Bug fix:** the scheduled-posts list view was rendering every coloured-background post's preview thumbnail with the same hardcoded dark slate, regardless of which Facebook preset was selected on the post. The Facebook send itself was always correct — the cosmetic preview just couldn't show the actual colour because the 41-preset table lived only inside the composer view, not in a shared location. Extracted the table to a new `OTTPOST_Backgrounds` helper class so the scheduled view can look up the same hex / gradient the composer's picker uses for each preset_id. Empty / unknown preset_id falls back to Solid Teal — same default the Graph API path uses at publish time, so the preview now matches what Facebook will actually render.

= 1.6.0 =
* Added a **Delete** button on each row of the Scheduled-posts list view, alongside the existing **Edit** and **Open ↗** buttons. Previously the only way to drop a scheduled post was to navigate to *One Two Three Post → All Posts* and use WordPress's standard row Trash action — a noticeable extra click whenever the operator just wanted to clear a queue entry from the natural "where I look at upcoming posts" view. Pre-publish posts (status not yet `publish`) are hard-deleted; already-sent posts move to trash so the operator can recover the record of an actual Facebook publish if needed. Native `confirm()` dialog before the action fires. Capability-gated to `edit_posts` and CSRF-nonced per row.

= 1.5.0 =
* Added three extension hooks in `OTTPOST_Scheduler::publish_post()` so add-on plugins (such as the separately-distributed One Two Three Post Pro) can plug a pre-publish validator chain into the existing scheduler without forking. New hooks: `ottpost_pre_publish_validate` (filter — return a `WP_Error` to block the publish and record the reason as a failure), `ottpost_publish_args` (filter — mutate the Graph API payload before send), and `ottpost_after_publish` (action — fires post-attempt with the result array, useful for log-table writes). No behaviour change for sites that don't have an add-on installed; the filters return their inputs unchanged when no listeners are attached.

= 1.4.0 =
* Renamed the internal code prefix from `OTP_*` / `otp_*` / `otp-*` to `OTTPOST_*` / `ottpost_*` / `ottpost-*` across all classes, constants, option keys, post meta, cron hooks, REST namespaces, and CSS / JS handles. The 3-character `otp` prefix was too short per wp.org's plugin-naming guidelines and risked colliding with other plugins (One Time Passwords, OpenTelemetry, etc.). All seven `class-otp-*.php` files were renamed to `class-ottpost-*.php` to match. **Breaking for existing installs:** option keys, the `otp_fb_post` custom post type slug, the `otp_scheduled` post status, and the `otp_fire_scheduled_posts` cron hook have all been renamed. After upgrading, you will need to re-connect Facebook (re-OAuth) and re-create any pending scheduled posts. New installs are unaffected.
* Fixed a pre-existing parse error in `admin/views/settings.php` (line 11 had a stray `<?php` opener inside an already-open PHP block — Settings page would crash on load). Caught by linting the renamed codebase.
* Added file-level `phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound` to the four view / uninstall files. Plugin Check reports those files' top-level `$variables` as potentially global, but they're always included from inside an admin class method (or, for uninstall.php, WP core's uninstall include) — function-scoped at runtime. Documented inline so future readers know why the rule is suppressed.

= 1.3.3 =
* Removed the redundant Plugin URI from the plugin header. wp.org's reviewer flagged Plugin URI and Author URI being identical (both pointed at the author site) — Plugin URI dropped, Author URI kept since it's the more accurate one for this header set.

= 1.3.2 =
* Plugin Check follow-up: moved the `slow_db_query_meta_query` phpcs:ignore directly above the `meta_query` array key in `OTTPOST_Scheduler::tick()` so the suppression actually applies (the previous placement above the `WP_Query` call was too high to silence the warning on the inner array key).

= 1.3.1 =
* Plugin Check pass. Removed the discouraged `load_plugin_textdomain()` call (WP 4.6+ auto-loads translations for wp.org plugins). Switched the OAuth-start `wp_redirect()` to `wp_safe_redirect()` with `www.facebook.com` whitelisted via the `allowed_redirect_hosts` filter. Added `wp_unslash()` ahead of `sanitize_text_field()` for `$_GET['cal_month']` and `$_GET['day']` on the Scheduled view. Documented intentional `$_POST['app_secret']` non-sanitization with a phpcs:ignore note (the value is encrypted before storage). Added phpcs:ignore comments for read-only `$_GET` flag checks and the `meta_query` / `meta_key` slow-query notices on small admin queries. Trimmed the readme short-description to fit the 150-char wp.org cap.

= 1.3.0 =
* Initial wp.org submission. Menu label changed from "FB Posts" to "One Two Three Post" so the plugin doesn't render an abbreviated trademark in the admin sidebar. Adds a bundled `uninstall.php` that drops plugin options, OAuth credentials, and the cron event when the user deletes the plugin from Plugins → Delete.

= 1.2.4 =
* List view now sorts the next post going out to the top; older / past posts follow in reverse chronological order.

= 1.2.3 =
* Optional First Comment field on the composer — posted automatically on the published post immediately after it goes live. Comment failures do not roll back the main post.

= 1.2.2 =
* Coloured-background picker now ships 41 known-good Facebook `text_format_preset_id` values with visual swatches plus a custom-preset-ID override.

= 1.2.0 =
* Edit on any saved post now returns to the composer with every field hydrated (page, message, image, schedule, type, background, first comment).
* Scheduled-posts list grows a Preview column with a thumbnail per row (image / coloured tile / text excerpt).

= 1.1.0 =
* New "Scheduled" admin page with a month Calendar view and a chronological List view of every drafted / scheduled / pending / sent post.
* Coloured-background text post type added to the composer.

= 1.0.0 =
* Initial release. Facebook OAuth flow (App ID + App Secret → user token → page tokens), composer with message + image + Page picker, three modes (Post Now, Schedule, Save as Draft), WP-Cron background worker, encrypted at-rest storage of App Secret and Page tokens.

== Upgrade Notice ==

= 1.3.0 =
First wp.org-distributed release. If you previously installed v1.2.x manually, upgrade in place — no settings reset is required.
