From ed4360afeaf54dc479b519080b26ec5a329844c9 Mon Sep 17 00:00:00 2001 From: Fraenkiman Date: Mon, 20 Jul 2026 00:51:34 +0200 Subject: [PATCH] Update mastodon to version 2.8.2 - An error in the budgeting/validity check for the maximum number of characters during post export has been fixed when a post contained a URL followed by punctuation marks. --- .../developer-docs/00-Mental-Model.md | 2 + .../mastodon/developer-docs/01-Process-Map.md | 4 +- .../03-Function-Process-Matrix.md | 4 +- .../developer-docs/04-API-Compatibility.md | 2 + .../05-Regression-Test-Matrix.md | 491 +++++++++--------- .../developer-docs/06-Process-Flow.md | 5 +- .../developer-docs/07-Function-Organigram.md | 412 +++++++-------- fp-plugins/mastodon/developer-docs/README.md | 29 +- .../developer-docs/check-consistency.php | 91 ++++ fp-plugins/mastodon/plugin.mastodon.php | 78 ++- .../simulate_mastodon_plugin.ph_ | 29 +- 11 files changed, 665 insertions(+), 482 deletions(-) diff --git a/fp-plugins/mastodon/developer-docs/00-Mental-Model.md b/fp-plugins/mastodon/developer-docs/00-Mental-Model.md index e5162138..bfd7e4bc 100644 --- a/fp-plugins/mastodon/developer-docs/00-Mental-Model.md +++ b/fp-plugins/mastodon/developer-docs/00-Mental-Model.md @@ -24,6 +24,8 @@ It maps FlatPress objects to Mastodon objects: Media export has one additional compatibility rule that developers must keep in mind: one Mastodon status may carry multiple images, or exactly one audio/video attachment, but not a mixed audio/video/image set. The plugin therefore collects all local media for change detection and diagnostics, then selects one exportable media family per status before upload: images first, otherwise one audio item, otherwise one video item with its poster sent only as an upload thumbnail. +Status export has a similar budgeting rule: Mastodon counts each recognized URL as the instance's `characters_reserved_per_url` value, but sentence punctuation immediately after a link is normal text. Local text limiting therefore runs through `plugin_mastodon_status_text_url_spans()` before `plugin_mastodon_status_text_length()` and `plugin_mastodon_limit_status_text()` so a trailing comma or full stop cannot be hidden inside the URL placeholder budget. + ```mermaid flowchart LR FP[FlatPress entries comments media tags] diff --git a/fp-plugins/mastodon/developer-docs/01-Process-Map.md b/fp-plugins/mastodon/developer-docs/01-Process-Map.md index bdcd283c..e6bd620b 100644 --- a/fp-plugins/mastodon/developer-docs/01-Process-Map.md +++ b/fp-plugins/mastodon/developer-docs/01-Process-Map.md @@ -229,7 +229,7 @@ Remote import must respect local deletion protection. A remote reply with a tomb flowchart TD Dirty[Dirty entry or comment] Parent{Reply target known?} - Text[Build Mastodon text with URL budget tags emoji] + Text[Build Mastodon text with URL spans budget tags emoji] MediaPlan{Entry media plan} Upload[POST api v2 media] Poll[GET api v1 media id until ready] @@ -256,6 +256,8 @@ flowchart TD Update -- failure after upload --> Cleanup ``` +URL-aware text budgeting runs before the media plan. `plugin_mastodon_status_text_url_spans()` extracts only the Mastodon-countable URL core, so punctuation after `https://...` such as commas and full stops remains in the normal text budget instead of being hidden in `characters_reserved_per_url`. + The media plan is one of the most important extension points. It compares attachment signatures and description signatures. If attachments did not change, the plugin can reuse remote media IDs. If only descriptions changed and the instance supports status `media_attributes` according to `api_versions[mastodon]` or the version fallback, it updates alt text without re-uploading. Otherwise it re-uploads. Before the media plan computes signatures or uploads anything, it applies the Mastodon status media-family policy: diff --git a/fp-plugins/mastodon/developer-docs/03-Function-Process-Matrix.md b/fp-plugins/mastodon/developer-docs/03-Function-Process-Matrix.md index 760c9510..d0dc220b 100644 --- a/fp-plugins/mastodon/developer-docs/03-Function-Process-Matrix.md +++ b/fp-plugins/mastodon/developer-docs/03-Function-Process-Matrix.md @@ -23,6 +23,7 @@ This matrix links implementation functions to the process map. It is intentional | plugin_mastodon_collect_local_entry_media | P7 | Extracts images/galleries/audio/video from FlatPress content. | FlatPress entry body and companion plugin formats | Builds candidate local media descriptors. | Local media extraction tests | | plugin_mastodon_build_entry_status_text | P5 | Builds Mastodon text from FlatPress entry. | Entry title/body, tags, URL budget, instance limits | Adds permalink and hashtag footer. | Text/tag/URL tests | | plugin_mastodon_build_comment_status_text | P6 | Builds Mastodon reply text from FlatPress comment. | Comment author/body, public comment URL | Localizes and converts text/emoji. | Comment export tests | +| plugin_mastodon_status_text_url_spans / plugin_mastodon_status_text_length / plugin_mastodon_limit_status_text | P5/P6 | Apply Mastodon-compatible URL placeholder budgeting before truncating status text. | Plain status text and `configuration.statuses.characters_reserved_per_url` | Treats trailing sentence punctuation as normal text so the server-side 500-character validator is not exceeded by URL-adjacent commas or full stops. | URL punctuation-budget regression tests | | plugin_mastodon_import_remote_entry | P3/P8 | Writes a remote top-level status as FlatPress entry. | Remote status JSON | Writes entry, media, tags, mappings and the imported status source footer built from `Status.url`. | Remote entry import tests | | plugin_mastodon_imported_status_footer_bbcode | P3 | Builds the imported Mastodon status source footer BBCode. | Mastodon `Status.url` | Emits `[url=... target=_blank rel="nofollow noopener noreferrer"]Mastodon[/url]` for the single toot/status. | Status-footer target tests | | plugin_mastodon_import_remote_comment / plugin_mastodon_protect_missing_local_exported_comment_by_remote_id | P4/P8/P9 | Imports a remote reply only after enforcing local-deletion ownership. | Remote context descendant, `comments_remote`, targeted comment shard | Recreates a pending-delete tombstone for a missing `source=local` file and refuses stale import without loading all shards. | Partial-sync and bypass-hook tests | @@ -52,7 +53,7 @@ flowchart TD CommentReplyGate[Comment/reply synchronization gate] CommentOptin[Visitor comment Mastodon opt-in] State[State scheduler logging filesystem guards] - Text[Text URL tags emoji BBCode HTML conversion] + Text[Text URL spans budgets tags emoji BBCode HTML conversion] Media[Media extraction validation upload import cleanup] API[HTTP transport Mastodon API wrappers] Sync[Import export deletion orchestrators] @@ -92,6 +93,7 @@ flowchart TD | OAuth scopes | `plugin_mastodon_oauth_scopes()`, discovery helpers | options `oauth_registered_scopes` | Scope discovery tests | | Profile widget | `plugin_mastodon_widget()`, `plugin_mastodon_refresh_profile_cache_for_sync()`, `plugin_mastodon_refresh_profile_cache_from_account()` | `profile/profile.json`, local avatar file | Widget cache/render/sync tests | | Text conversion | BBCode/HTML/text helper functions | usually no state | HTML/BBCode/URL/tag/emoji tests | +| URL-adjacent punctuation | `plugin_mastodon_status_text_url_spans()`, `plugin_mastodon_status_text_length()`, `plugin_mastodon_limit_status_text()` | None | URL punctuation-budget tests | ## Reading guidance diff --git a/fp-plugins/mastodon/developer-docs/04-API-Compatibility.md b/fp-plugins/mastodon/developer-docs/04-API-Compatibility.md index 400fa4c4..68f6aaa0 100644 --- a/fp-plugins/mastodon/developer-docs/04-API-Compatibility.md +++ b/fp-plugins/mastodon/developer-docs/04-API-Compatibility.md @@ -108,6 +108,8 @@ The plugin reads `/api/v2/instance` with a short instance-information timeout an | Video size limit | `configuration.media_attachments.video_size_limit` | No local size rejection when unknown. | | Audio size limit | `configuration.media_attachments.audio_size_limit` | Falls back to video size if explicit audio limit is absent; otherwise no local rejection. | +For status text length, `configuration.statuses.characters_reserved_per_url` is applied only to the URL entity that Mastodon will parse. `plugin_mastodon_status_text_url_spans()` intentionally leaves trailing sentence punctuation such as `,` or `.` outside the URL span, so those characters remain part of the normal text budget and the local export limit matches Mastodon server validation more closely. + ## Internal budgets and operational limits | Name | Value | Purpose | diff --git a/fp-plugins/mastodon/developer-docs/05-Regression-Test-Matrix.md b/fp-plugins/mastodon/developer-docs/05-Regression-Test-Matrix.md index e1abb242..f626e911 100644 --- a/fp-plugins/mastodon/developer-docs/05-Regression-Test-Matrix.md +++ b/fp-plugins/mastodon/developer-docs/05-Regression-Test-Matrix.md @@ -6,12 +6,12 @@ Normal run checked for this documentation set after the exported-comment deletio ```text php simulate_mastodon_plugin.php --summary Exit-code: 0 -[OK]: 272 +[OK]: 273 [FAIL]: 0 [WARN]: 0 [SKIP]: 0 ``` -Static `test_result()` calls found in the script: `273`. +Static `test_result()` calls found in the script: `274`. One test is optional and only runs with `--live-auth`: ```text @@ -24,13 +24,13 @@ The catalog below is generated from all static `test_result()` calls and therefo | Category | Static tests | | ---------------------------------- | ------------ | -| Regular simulation tests | 272 | +| Regular simulation tests | 273 | | Optional live/auth smoke | 1 | -| Total static `test_result()` calls | 273 | +| Total static `test_result()` calls | 274 | ## What the harness proves well -The harness now also checks the real `comment_validate` filter chain with CommentCenter moderation, the direct save path with the CommentCenter filter explicitly removed, preservation of the checked Mastodon opt-in after unrelated validation errors, explicit hook priorities/argument counts, and cleanup of rejected or externally orphaned pending grants. The complete simulator remains green when CommentCenter is absent from the active plugin configuration; CommentCenter-specific assertions become not applicable while the direct FlatPress save path remains fully exercised. It also checks that imported Mastodon status footer links render with `target="_blank"` and `rel="nofollow noopener noreferrer"` while those BBCode attributes are stripped from outbound Mastodon text. It also checks that advanced comment-shard maintenance controls are not embedded in the main settings template, are available from a dedicated maintenance template, that notification-based reply hints import old-thread replies before the slower rotation fallback, and that the central `disable_comment_reply_sync` gate disables FlatPress comment export, Mastodon reply import, reply-notification/context imports and reply deletion follow-ups while keeping entry synchronization active, that the optional `{comment_mastodon}` visitor opt-in is shown only while local comments may be exported, that FlatPress/admin comments with a stored `LOGGEDIN` marker export without a public visitor marker, that visitor opt-ins are persisted even before credentials are complete, that local visitor comments without a stored opt-in marker stay local even when saved during an admin session, and that opted-in visitor replies are not blocked by non-opted-in local parents, and that the optional explicit one-way mode blocks Mastodon-to-FlatPress imports while preserving FlatPress-to-Mastodon exports, keeps hidden import settings intact in the admin save path, hides import-only admin UI output, hides import-only companion-plugin diagnostics while keeping export helpers, re-exports local objects after remote deletion, and keeps locally deleted imported external Mastodon replies tombstoned even when the remote author edits the reply before the next content sync. It also verifies that Mastodon instance capability detection prefers machine-readable `api_versions[mastodon]`, handles nightly version strings, preserves compact `configuration.accounts` snapshots, loads the widget stylesheet through `plugin_mastodon_head()` as a versioned `res/mastodon.css` asset, refreshes the profile-widget cache during automatic and manual one-way sync paths, sends `exclude_direct=true` only when account-status support is known, retries without it for compatible-server rejections, ignores notification fallback payloads when the normal mention status is present, preserves long remote media descriptions, avoids repeated failed `/api/v2/instance` requests within one PHP request, keeps safe defaults when instance information is temporarily unavailable, version-gates unattached media cleanup deletes, and renders the compact Mastodon profile widget solely from a local public profile cache with a locally stored avatar. +The harness now also checks the real `comment_validate` filter chain with CommentCenter moderation, the direct save path with the CommentCenter filter explicitly removed, preservation of the checked Mastodon opt-in after unrelated validation errors, explicit hook priorities/argument counts, and cleanup of rejected or externally orphaned pending grants. The complete simulator remains green when CommentCenter is absent from the active plugin configuration; CommentCenter-specific assertions become not applicable while the direct FlatPress save path remains fully exercised. It also checks that imported Mastodon status footer links render with `target="_blank"` and `rel="nofollow noopener noreferrer"` while those BBCode attributes are stripped from outbound Mastodon text. It also checks URL-aware status budgeting at punctuation boundaries, so commas and full stops directly after links stay outside the fixed Mastodon URL placeholder. It also checks that advanced comment-shard maintenance controls are not embedded in the main settings template, are available from a dedicated maintenance template, that notification-based reply hints import old-thread replies before the slower rotation fallback, and that the central `disable_comment_reply_sync` gate disables FlatPress comment export, Mastodon reply import, reply-notification/context imports and reply deletion follow-ups while keeping entry synchronization active, that the optional `{comment_mastodon}` visitor opt-in is shown only while local comments may be exported, that FlatPress/admin comments with a stored `LOGGEDIN` marker export without a public visitor marker, that visitor opt-ins are persisted even before credentials are complete, that local visitor comments without a stored opt-in marker stay local even when saved during an admin session, and that opted-in visitor replies are not blocked by non-opted-in local parents, and that the optional explicit one-way mode blocks Mastodon-to-FlatPress imports while preserving FlatPress-to-Mastodon exports, keeps hidden import settings intact in the admin save path, hides import-only admin UI output, hides import-only companion-plugin diagnostics while keeping export helpers, re-exports local objects after remote deletion, and keeps locally deleted imported external Mastodon replies tombstoned even when the remote author edits the reply before the next content sync. It also verifies that Mastodon instance capability detection prefers machine-readable `api_versions[mastodon]`, handles nightly version strings, preserves compact `configuration.accounts` snapshots, loads the widget stylesheet through `plugin_mastodon_head()` as a versioned `res/mastodon.css` asset, refreshes the profile-widget cache during automatic and manual one-way sync paths, sends `exclude_direct=true` only when account-status support is known, retries without it for compatible-server rejections, ignores notification fallback payloads when the normal mention status is present, preserves long remote media descriptions, avoids repeated failed `/api/v2/instance` requests within one PHP request, keeps safe defaults when instance information is temporarily unavailable, version-gates unattached media cleanup deletes, and renders the compact Mastodon profile widget solely from a local public profile cache with a locally stored avatar. | Area | What is real | What is simulated | | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | @@ -135,243 +135,244 @@ The media-family policy is protected by dedicated tests. These tests call the re | 3894 | FlatPress entry without tag BBCode has no Mastodon hashtag footer | | 3907 | Mastodon head metadata uses the configured instance URL and username | | 3920 | Mastodon head metadata tolerates usernames stored with a leading at-sign | -| 3933 | Mastodon head metadata stays silent when no username is configured | -| 3947 | Actual FlatPress comment exports as readable Mastodon reply | -| 3957 | FlatPress comment export appends the public comment link on a new line | -| 3968 | FlatPress entry export converts Emoticons plugin shortcodes in titles and bodies to Unicode emoji for Mastodon | -| 3987 | FlatPress comment export converts Emoticons plugin shortcodes in author lines and bodies to Unicode emoji for Mastodon | -| 4005 | Long local entry export is limited even without a public permalink | -| 4014 | FlatPress single-image entry exposes one uploadable media item | -| 4024 | FlatPress gallery entry exposes uploadable media items and keeps raw gallery BBCode out of Mastodon text | -| 4077 | FlatPress AudioVideo entry exposes described audio/video media items and strips raw player BBCode from Mastodon text | -| 4097 | AudioVideo BBCode parser renders optional description endtags as media attributes and keeps legacy single tags working | -| 4133 | Mastodon media upload sends AudioVideo files with descriptions and video thumbnail | -| 4158 | Mastodon media planner exports only images when an entry mixes image, audio and video media | -| 4169 | Mastodon media planner exports exactly one audio attachment when an entry mixes audio and video media | -| 4183 | Mastodon media planner exports one video and keeps its poster as an upload thumbnail only | -| 4196 | Mastodon media planner keeps multiple images up to the Mastodon media limit | -| 4225 | Mastodon export uploads only the selected audio item from an audio/video FlatPress entry | -| 4255 | Mastodon export sends a video poster as upload thumbnail instead of a second media ID | -| 4287 | Mastodon single image imports into a FlatPress image tag and stored file | -| 4311 | Mastodon import preserves long remote media descriptions without corrupting FlatPress image markup | -| 4355 | Mastodon multiple images import into a FlatPress gallery with captions | -| 4404 | Mastodon audio and video attachments import into FlatPress AudioVideo BBCode and stored files | -| 4443 | Mastodon audio/video import retries alternate direct media URLs when the first download candidate fails | -| 4457 | First sync is immediately due | -| 4460 | Next day after 23:00 is due | -| 4461 | Before configured time is not due | -| 4482 | Scheduled synchronization due checks use stored UTC time regardless of PHP default timezone | -| 4504 | Pending deletion synchronization due checks use UTC cooldowns regardless of PHP default timezone | -| 4535 | Admin state timestamps are formatted with FlatPress timeoffset regardless of PHP default timezone | -| 4550 | Scheduled content synchronization uses a short file/APCu cooldown guard | -| 4567 | Full Mastodon state fallback is not stored in APCu | -| 4599 | Central Mastodon rate-limit guard stops requests after the per-run request budget | -| 4633 | Central Mastodon rate-limit guard stops media uploads after the per-run media budget | -| 4657 | Central Mastodon rate-limit guard stops status deletions after the per-run delete budget | -| 4683 | Cached Mastodon versions before 4.4 delete statuses without the delete_media query parameter | -| 4707 | Status deletion retries without delete_media when an older Mastodon server rejects the query parameter | -| 4737 | Persistent Mastodon media-upload window budget is shared across forced runs | -| 4767 | Persistent media-upload window budget is written to the synchronization last error | -| 4794 | Persistent Mastodon delete window budget is shared across forced runs | -| 4818 | Persistent delete window budget is written to the synchronization last error | -| 4845 | Persistent Mastodon account-status paging window budget is shared across forced runs | -| 4868 | Persistent account-status paging window budget is written to the synchronization last error | -| 4891 | Admin synchronization time converts stored UTC to FlatPress local time and back | -| 4914 | Admin synchronization time conversion supports fractional FlatPress offsets | -| 4930 | Sync start date normalization accepts valid ISO dates | -| 4936 | Local and remote date helpers derive stable date keys | -| 4945 | Remote-to-local update toggle normalizes safely | -| 4956 | Comment-as-entry import toggle normalizes safely | -| 4978 | Comment/reply synchronization disable toggle normalizes and saves safely | -| 5009 | Mastodon comment opt-in is required only when local comments may be exported | -| 5035 | Mastodon comment opt-in markup is shown only while comment/reply sync is active | -| 5052 | State entry mapping created | -| 5053 | State comment mapping created | -| 5055 | State comment-to-reply opt-in marker created | -| 5056 | Local visitor comment without Mastodon opt-in is not eligible for Mastodon export | -| 5107 | Mastodon runtime files in fp-content/plugin_mastodon use FlatPress FILE_PERMISSIONS | -| 5146 | Initial local thread comment/reply sync is enabled | -| 5305 | OAuth code exchange | -| 5313 | State cache stays fresh after writing state.json | -| 5334 | Pretty-printed legacy state.json remains readable after compact-state optimization | -| 5382 | State write persists compact split state.json while preserving round-trip mappings | -| 5447 | Split comment shards support bounded per-entry state reads | -| 5471 | Partial split-state writes preserve unloaded comment shards | -| 5515 | Legacy inline comment state migrates to per-entry comment shards on write | -| 5590 | Legacy inline migration preserves non-contiguous comments and creates a migration backup | -| 5649 | Comment-shard diagnostics detect stale metadata and repair the reverse index from shards | -| 5670 | CLI comment-shard diagnostics entry point returns a machine-checkable status | -| 5705 | Shard-write failures leave the main state unchanged and return failure | -| 5744 | Main-state write failures after shard writes remain repairable from shard files | -| 5781 | Legacy combined stats are migrated into separate content and deletion counters | -| 5805 | Instance configuration cache avoids repeated /api/v2/instance requests | -| 5832 | Nightly Mastodon versions use cached api_versions for status edit and delete capabilities | -| 5855 | Machine-readable api_versions are preferred over complex human-readable Mastodon version strings | -| 5884 | api_versions below delete_media support suppress the delete_media query even with a nightly version string | -| 5910 | api_versions below unattached media delete support skip uploaded media cleanup DELETE requests | -| 5933 | Cached Mastodon versions before 4.4 skip unattached media DELETE cleanup requests | -| 5967 | Unknown unattached media delete capability stays best-effort without an instance lookup | -| 6006 | Mastodon API version 4 deletes unattached uploaded media during cleanup | -| 6037 | Mastodon 4.6 configuration.accounts limits survive compact instance snapshots | -| 6069 | Mastodon 4.6.0-nightly account-status import sends exclude_direct for privacy | -| 6101 | Mastodon API version 10 account-status import sends exclude_direct even on forked version strings | -| 6137 | Account-status import retries without exclude_direct when a compatible server rejects the parameter | -| 6172 | Cached Mastodon versions before 4.6 keep account-status import compatible without exclude_direct | -| 6201 | Failed instance-information lookups are negatively cached per request and fall back to defaults | -| 6238 | Mastodon URL budgeting respects characters_reserved_per_url when limiting plain-text exports | -| 6263 | Configured credentials verify (read-only smoke test) | -| 6275 | Entry export uses clean Mastodon text and suppresses localhost permalinks | -| 6283 | Comment export is reply-like, localized and emoji-aware | -| 6320 | Remote entry imported through sync with Mastodon media visible in FlatPress | -| 6326 | Deletion sync is queued for a follow-up request instead of running inside the content sync request | -| 6332 | Content sync updates the synchronization timestamp and counters while leaving the deletion timestamp for the follow-up request | -| 6351 | Private mention status is skipped during Mastodon import | -| 6357 | Remote imported entry keeps a stable FlatPress date | -| 6363 | Remote comment imported through sync | -| 6369 | Nested remote comment keeps parent mapping metadata | -| 6375 | Private mention reply is skipped during Mastodon import | -| 6413 | Entry export creates a top-level Mastodon status | -| 6419 | Comment to entry exports with in_reply_to_id on the entry status | -| 6425 | Comment to comment exports with in_reply_to_id on the parent reply | -| 6431 | Entry and comment exports include the configured Mastodon language code | -| 6450 | Deletion sync waits at least five minutes after a completed content sync | -| 6502 | Create and update status requests include the configured Mastodon language code | -| 6545 | Initial Mastodon media uploads include the attachment description in POST /api/v2/media | -| 6577 | Unchanged entry media is reused without a new upload when only the post text changes | -| 6650 | Mastodon 4.1+ updates changed image descriptions through status media_attributes without re-uploading media | -| 6687 | Older Mastodon versions fall back to a fresh upload when only the media description changes | -| 6774 | Full local-to-remote sync reuses stored media IDs when the attachments did not change | -| 6874 | Full sync updates changed media descriptions through status media_attributes on Mastodon 4.1+ | -| 6997 | Batch entry export keeps older FlatPress entries below newer ones on Mastodon | -| 7100 | New local comment on an already synchronized older entry is exported to Mastodon | -| 7179 | Disabled comment/reply sync keeps local comments off Mastodon | -| 7258 | Disabled comment/reply sync skips remote reply deletion follow-up | -| 7354 | Known synchronized entry mappings older than the sync start date do not trigger context refreshes | -| 7428 | Scheduled content synchronization respects the automatic recent-content window | -| 7454 | Productive content-sync workset loads only active scheduled comment shards | -| 7513 | Normal manual synchronization bypasses the daily due check but still respects the automatic window | -| 7575 | Explicit full manual synchronization bypasses the automatic window while keeping normal limits | -| 7654 | Older changed mapped FlatPress entries are synchronized through the dirty queue | -| 7706 | Post-success comment hook queues older changed mapped comments | -| 7736 | Scheduled sync updates older dirty comments through direct YY/MM dirty-parent candidates | -| 7783 | Remote-write guard suppresses dirty tracking for Mastodon-owned entry_save calls | -| 7872 | Large scheduled dirty-tracking sync parses only active-window and dirty entries | -| 7955 | Optional old-thread reply checks rotate through known synchronized threads | -| 7996 | Disabled old-thread reply checks do not refresh known synchronized entry contexts | -| 8077 | Disabled comment/reply sync imports Mastodon entries without fetching reply contexts or notifications | -| 8156 | Automatic scheduled synchronization rotates known synchronized threads through the full sync path | -| 8242 | Notification hints import a new reply on an old mapped Mastodon entry without context rotation | -| 8258 | Notification fallback payloads are ignored when a normal mention status is available | -| 8327 | Notification hints import a new reply on an old mapped Mastodon reply with reply metadata | -| 8432 | Notification context hints use the old-thread budget before normal rotation | -| 8483 | Reply-notification polling is skipped until read:notifications is authorized | -| 8606 | Remote updates do not overwrite existing local content when the toggle is disabled | -| 8626 | Remote updates overwrite existing local content when the toggle is enabled | -| 8651 | FlatPress single-image entry exports Mastodon media_ids and strips raw image BBCode | -| 8662 | FlatPress gallery entry exports Mastodon media_ids up to the instance limit and strips raw gallery BBCode | -| 8800 | Sync start date filters local exports by entry and comment date | -| 8886 | Sync start date filters remote imports by status and reply date | -| 8978 | Remote Mastodon imports honor the FlatPress timeoffset for stored entry/comment dates and ordering keys | -| 9049 | Remote sync start filtering respects the FlatPress timeoffset near midnight | -| 9114 | Sync reports local export failures instead of silently succeeding | -| 9132 | Shared-request synchronization refreshes the PHP execution budget for long Mastodon work | -| 9215 | Synchronized local comments are not imported as duplicate entries from Mastodon by default | -| 9307 | Synchronized local comments may be imported as entries when the toggle is enabled | -| 9442 | Local FlatPress comments on imported Mastodon entries sync back to Mastodon, including nested replies | -| 9518 | Asynchronous Mastodon media uploads are polled until the attachment is ready | -| 9542 | Long-running Mastodon media uploads refresh the PHP execution budget for upload and polling | -| 9614 | Slow asynchronous Mastodon video uploads keep polling beyond the old short retry window | -| 9680 | Mastodon audio uploads keep polling even when pending responses have no preview_url | -| 9745 | Follow-up deletion sync removes remote Mastodon content after local FlatPress deletion | -| 9770 | Follow-up deletion sync updates last_deletion_run without overwriting the last synchronization timestamp or content counters | -| 9826 | Deletion sync falls back to DELETE without delete_media for older Mastodon servers | -| 9889 | Follow-up deletion sync removes mirrored FlatPress content after remote Mastodon deletion | -| 9944 | Deletion sync skips old locally deleted mappings outside the sync start date window | -| 9994 | Deletion sync skips old remotely mirrored mappings outside the sync start date window | -| 10051 | Scheduled deletion sync skips remote lookups outside the automatic scheduled window | -| 10124 | Scheduled deletion sync still propagates old local deletions outside the automatic window | -| 10185 | Large scheduled deletion syncs resume from the saved entry cursor | -| 10269 | Large scheduled deletion syncs resume from the saved comment cursor inside one entry shard | -| 10314 | Disabling deletion synchronization clears pending delete work without issuing deletion requests | -| 10334 | Content sync before deletion sync protects a locally deleted exported FlatPress comment from stale re-import | -| 10353 | Deletion sync after the protected content sync reattaches the imported descendant reply to the synchronized entry status and keeps a later verification pending | -| 10396 | Normal manual partial content sync keeps an old locally deleted exported comment tombstoned without stale re-import | -| 10435 | Automatic scheduled partial content sync keeps an old locally deleted exported comment tombstoned without stale re-import | -| 10472 | Remote import defensively tombstones a missing source=local comment when the deletion hook was bypassed | -| 10499 | Comment mapping rejects duplicate remote ids and removes the previous reverse id on a legitimate remap | -| 10526 | Deletion sync keeps imported descendants for a later follow-up verification when Mastodon still returns the child reply once | -| 10543 | Tombstones prevent re-importing the deleted exported parent comment while descendant rechecks are still pending | -| 10556 | A later follow-up deletion sync removes the imported descendant once Mastodon reports the child reply missing | -| 10587 | Content sync before deletion sync also protects a locally deleted exported FlatPress comment when the imported descendant reply already has its own child | -| 10607 | Deletion sync after the protected content sync keeps the descendant child below the reattached reply and verifies both remote replies in the same pass | -| 10647 | Deletion sync removes imported descendants immediately when Mastodon already reports the child reply missing in the first follow-up pass | -| 10678 | Hybrid descendant rechecks keep only the direct child queued after the first stale follow-up pass of a deeper reply chain | -| 10697 | Hybrid descendant rechecks remove the direct child and its direct child within the same targeted follow-up request | -| 10722 | Locally deleted imported external Mastodon reply is tombstoned immediately and not re-imported unchanged | -| 10747 | Locally deleted imported external Mastodon reply is not re-imported after the remote author edits it | -| 10770 | Deletion sync treats locally deleted imported remote replies as local ignore decisions without remote DELETE | -| 10852 | Remote Mastodon comment updates increment the local-comment update counter instead of the entry counter | -| 10893 | Admin assignment exposes split sync counters, local timestamps, old-thread and deletion-sync options | -| 10986 | Admin maintenance actions are separated into a dedicated template reached from the main plugin page | -| 11077 | Media upload cleanup deletes already uploaded attachments when a later media upload fails | -| 11144 | Media upload cleanup deletes uploaded attachments when final status creation fails | -| 11202 | OAuth scope discovery prefers the profile scope on current Mastodon instances | -| 11248 | OAuth scope discovery falls back to read:accounts plus notifications on older Mastodon instances | -| 11300 | Existing registered apps keep the legacy read:accounts scope until they are re-registered | -| 11391 | Instance information refresh persists a compact snapshot including the exact Mastodon version | -| 11437 | Admin assignment exposes cached instance-information rows without triggering another live instance request | -| 11454 | Changing the configured instance URL invalidates the saved instance-information snapshot | -| 11492 | Sync-related instance limit helpers reuse the stored instance snapshot without another /api/v2/instance request | -| 11514 | Manual sync exports FlatPress comments on remote-sourced entries to Mastodon replies | -| 11532 | Replies to FlatPress comments on remote-sourced entries are exported as replies to the parent Mastodon reply | -| 11562 | Unsynchronized local parent comment is exported before local child reply | -| 11582 | Non-forced sync exports FlatPress comments on remote-sourced entries to Mastodon replies | -| 11605 | Manual sync exports Emoticons plugin shortcodes from FlatPress entries to Unicode emoji in Mastodon status requests | -| 11621 | Manual sync exports Emoticons plugin shortcodes from FlatPress comments to Unicode emoji in Mastodon reply requests | -| 11642 | Manual sync imports another Mastodon member reply to an exported FlatPress comment only as a FlatPress comment when comment-as-entry import is disabled | -| 11668 | Non-forced sync imports another Mastodon member reply to an exported FlatPress comment only as a FlatPress comment when comment-as-entry import is disabled | -| 11691 | Another Mastodon member reply to an exported FlatPress comment is still imported as a FlatPress comment when comment-as-entry import is enabled | -| 11708 | Disabling the quote option imports a Mastodon reply to an exported FlatPress comment without a leading quote block | -| 11722 | Imported Mastodon replies to another Mastodon reply quote the replied-to Mastodon user and text by default | -| 11741 | Disabling the quote option imports Mastodon reply-to-reply comments without a leading quote block | -| 11755 | Manual sync imports a Mastodon self-reply to an exported FlatPress comment only as a FlatPress comment | -| 11779 | Non-forced sync imports a Mastodon self-reply to an exported FlatPress comment only as a FlatPress comment | -| 11832 | Scheduler state is written as a compact summary without full mapping arrays | -| 11851 | Fresh scheduler-state read uses the APCu-capable FlatPress I/O path and does not load full state.json | -| 11880 | Stale scheduler-state falls back to the full state and rebuilds the summary | -| 11901 | Manual admin synchronization still loads the full state before reporting configuration errors | -| 11920 | sync.log uses append-only writes with size-based rotation | -| 11946 | Large skip volumes are logged as aggregate summaries | -| 11959 | Small 300x10 state keeps scheduler-state compact and disables full APCu fallback | -| 11981 | Fresh small scheduler-state read avoids full state.json and uses APCu-capable file I/O | -| 12004 | Large 3000x10 state keeps scheduler-state compact and disables full APCu fallback | -| 12027 | Fresh large scheduler-state read avoids full state.json and uses APCu-capable file I/O | -| 12143 | Automatic scheduled sync exports a new current comment on an old mapped entry through dirty_comments | -| 12246 | Authenticated FlatPress comments export after a stored LOGGEDIN marker without visitor opt-in | -| 12357 | Admin session does not grant Mastodon export to visitor comments without opt-in | -| 12423 | Visitor Mastodon opt-in checkbox preserves POST state and hook priorities/signatures are explicit | -| 12525 | Visitor opt-in persists through the direct FlatPress save path when CommentCenter is disabled | -| 12704 | CommentCenter preserves visitor Mastodon opt-in until admin approval and export | -| 12842 | CommentCenter rejection and bounded pruning remove orphaned pending opt-in grants | -| 12948 | Visitor opt-in is stored without complete credentials and later permits comment export | -| 13053 | Opted-in visitor replies export even when their local parent has no Mastodon opt-in | -| 13114 | One-way mode option is disabled by default, normalized, assigned to admin UI, templated and translated | -| 13154 | One-way admin save preserves hidden import options while keeping one-way mode enabled | -| 13171 | One-way admin save preserves hidden import options when one-way mode is disabled in the same submit | -| 13191 | Bidirectional admin save still stores visible unchecked import options as disabled | -| 13223 | One-way admin template hides import controls, notification hints and import-only counters | -| 13249 | One-way admin template keeps export, OAuth, instance, token, state and deletion outputs visible | -| 13265 | One-way admin assignment hides import-only companion plugins and uses one-way companion intro | -| 13371 | One-way mode blocks Mastodon-to-FlatPress imports while FlatPress-to-Mastodon export still runs | -| 13458 | Automatic one-way content sync refreshes the Mastodon widget profile cache without remote import reads | -| 13533 | One-way deletion sync keeps local content, removes stale remote mappings and queues re-export | -| 13608 | One-way content sync re-exports local objects whose remote mappings were unlinked after remote deletion | -| 13677 | One-way pending descendant rechecks keep local comments and queue them for re-export instead of deleting them | -| 13698 | Mastodon widget remains hidden and performs no HTTP requests while its local profile cache is missing | -| 13741 | Mastodon widget profile cache is refreshed from verify_credentials and stores only public profile data plus a local avatar | -| 13766 | Mastodon widget renders compact local-cache markup without remote API calls or inline CSS | -| 13790 | Mastodon widget stylesheet is loaded by plugin_mastodon_head as a versioned CSS asset | -| 13805 | Mastodon widget profile refresh reuses the cached avatar when the Mastodon avatar URL is unchanged | -| 13832 | Mastodon widget uses a localized fallback avatar alt text for Mastodon 4.0-4.5 account payloads without avatar_description | -| 13851 | Mastodon widget hides incomplete local profile caches instead of falling back to remote avatar URLs | -| 13876 | Mastodon widget is registered and translated in all FlatPress plugin language files | +| 3933 | Mastodon head metadata omits profile metadata when no username is configured | +| 3948 | Actual FlatPress comment exports as readable Mastodon reply | +| 3958 | FlatPress comment export appends the public comment link on a new line | +| 3969 | FlatPress entry export converts Emoticons plugin shortcodes in titles and bodies to Unicode emoji for Mastodon | +| 3988 | FlatPress comment export converts Emoticons plugin shortcodes in author lines and bodies to Unicode emoji for Mastodon | +| 4006 | Long local entry export is limited even without a public permalink | +| 4015 | FlatPress single-image entry exposes one uploadable media item | +| 4025 | FlatPress gallery entry exposes uploadable media items and keeps raw gallery BBCode out of Mastodon text | +| 4078 | FlatPress AudioVideo entry exposes described audio/video media items and strips raw player BBCode from Mastodon text | +| 4098 | AudioVideo BBCode parser renders optional description endtags as media attributes and keeps legacy single tags working | +| 4134 | Mastodon media upload sends AudioVideo files with descriptions and video thumbnail | +| 4159 | Mastodon media planner exports only images when an entry mixes image, audio and video media | +| 4170 | Mastodon media planner exports exactly one audio attachment when an entry mixes audio and video media | +| 4184 | Mastodon media planner exports one video and keeps its poster as an upload thumbnail only | +| 4197 | Mastodon media planner keeps multiple images up to the Mastodon media limit | +| 4226 | Mastodon export uploads only the selected audio item from an audio/video FlatPress entry | +| 4256 | Mastodon export sends a video poster as upload thumbnail instead of a second media ID | +| 4288 | Mastodon single image imports into a FlatPress image tag and stored file | +| 4312 | Mastodon import preserves long remote media descriptions without corrupting FlatPress image markup | +| 4356 | Mastodon multiple images import into a FlatPress gallery with captions | +| 4405 | Mastodon audio and video attachments import into FlatPress AudioVideo BBCode and stored files | +| 4444 | Mastodon audio/video import retries alternate direct media URLs when the first download candidate fails | +| 4458 | First sync is immediately due | +| 4461 | Next day after 23:00 is due | +| 4462 | Before configured time is not due | +| 4483 | Scheduled synchronization due checks use stored UTC time regardless of PHP default timezone | +| 4505 | Pending deletion synchronization due checks use UTC cooldowns regardless of PHP default timezone | +| 4536 | Admin state timestamps are formatted with FlatPress timeoffset regardless of PHP default timezone | +| 4551 | Scheduled content synchronization uses a short file/APCu cooldown guard | +| 4568 | Full Mastodon state fallback is not stored in APCu | +| 4600 | Central Mastodon rate-limit guard stops requests after the per-run request budget | +| 4634 | Central Mastodon rate-limit guard stops media uploads after the per-run media budget | +| 4658 | Central Mastodon rate-limit guard stops status deletions after the per-run delete budget | +| 4684 | Cached Mastodon versions before 4.4 delete statuses without the delete_media query parameter | +| 4708 | Status deletion retries without delete_media when an older Mastodon server rejects the query parameter | +| 4738 | Persistent Mastodon media-upload window budget is shared across forced runs | +| 4768 | Persistent media-upload window budget is written to the synchronization last error | +| 4795 | Persistent Mastodon delete window budget is shared across forced runs | +| 4819 | Persistent delete window budget is written to the synchronization last error | +| 4846 | Persistent Mastodon account-status paging window budget is shared across forced runs | +| 4869 | Persistent account-status paging window budget is written to the synchronization last error | +| 4892 | Admin synchronization time converts stored UTC to FlatPress local time and back | +| 4915 | Admin synchronization time conversion supports fractional FlatPress offsets | +| 4931 | Sync start date normalization accepts valid ISO dates | +| 4937 | Local and remote date helpers derive stable date keys | +| 4946 | Remote-to-local update toggle normalizes safely | +| 4957 | Comment-as-entry import toggle normalizes safely | +| 4979 | Comment/reply synchronization disable toggle normalizes and saves safely | +| 5010 | Mastodon comment opt-in is required only when local comments may be exported | +| 5036 | Mastodon comment opt-in markup is shown only while comment/reply sync is active | +| 5053 | State entry mapping created | +| 5054 | State comment mapping created | +| 5056 | State comment-to-reply opt-in marker created | +| 5057 | Local visitor comment without Mastodon opt-in is not eligible for Mastodon export | +| 5108 | Mastodon runtime files in fp-content/plugin_mastodon use FlatPress FILE_PERMISSIONS | +| 5147 | Initial local thread comment/reply sync is enabled | +| 5306 | OAuth code exchange | +| 5314 | State cache stays fresh after writing state.json | +| 5335 | Pretty-printed legacy state.json remains readable after compact-state optimization | +| 5383 | State write persists compact split state.json while preserving round-trip mappings | +| 5448 | Split comment shards support bounded per-entry state reads | +| 5472 | Partial split-state writes preserve unloaded comment shards | +| 5516 | Legacy inline comment state migrates to per-entry comment shards on write | +| 5591 | Legacy inline migration preserves non-contiguous comments and creates a migration backup | +| 5650 | Comment-shard diagnostics detect stale metadata and repair the reverse index from shards | +| 5671 | CLI comment-shard diagnostics entry point returns a machine-checkable status | +| 5706 | Shard-write failures leave the main state unchanged and return failure | +| 5745 | Main-state write failures after shard writes remain repairable from shard files | +| 5782 | Legacy combined stats are migrated into separate content and deletion counters | +| 5806 | Instance configuration cache avoids repeated /api/v2/instance requests | +| 5833 | Nightly Mastodon versions use cached api_versions for status edit and delete capabilities | +| 5856 | Machine-readable api_versions are preferred over complex human-readable Mastodon version strings | +| 5885 | api_versions below delete_media support suppress the delete_media query even with a nightly version string | +| 5911 | api_versions below unattached media delete support skip uploaded media cleanup DELETE requests | +| 5934 | Cached Mastodon versions before 4.4 skip unattached media DELETE cleanup requests | +| 5968 | Unknown unattached media delete capability stays best-effort without an instance lookup | +| 6007 | Mastodon API version 4 deletes unattached uploaded media during cleanup | +| 6038 | Mastodon 4.6 configuration.accounts limits survive compact instance snapshots | +| 6070 | Mastodon 4.6.0-nightly account-status import sends exclude_direct for privacy | +| 6102 | Mastodon API version 10 account-status import sends exclude_direct even on forked version strings | +| 6138 | Account-status import retries without exclude_direct when a compatible server rejects the parameter | +| 6173 | Cached Mastodon versions before 4.6 keep account-status import compatible without exclude_direct | +| 6202 | Failed instance-information lookups are negatively cached per request and fall back to defaults | +| 6239 | Mastodon URL budgeting respects characters_reserved_per_url when limiting plain-text exports | +| 6258 | Mastodon URL budgeting counts trailing punctuation outside the URL placeholder | +| 6288 | Configured credentials verify (read-only smoke test) | +| 6300 | Entry export uses clean Mastodon text and suppresses localhost permalinks | +| 6308 | Comment export is reply-like, localized and emoji-aware | +| 6345 | Remote entry imported through sync with Mastodon media visible in FlatPress | +| 6351 | Deletion sync is queued for a follow-up request instead of running inside the content sync request | +| 6357 | Content sync updates the synchronization timestamp and counters while leaving the deletion timestamp for the follow-up request | +| 6376 | Private mention status is skipped during Mastodon import | +| 6382 | Remote imported entry keeps a stable FlatPress date | +| 6388 | Remote comment imported through sync | +| 6394 | Nested remote comment keeps parent mapping metadata | +| 6400 | Private mention reply is skipped during Mastodon import | +| 6438 | Entry export creates a top-level Mastodon status | +| 6444 | Comment to entry exports with in_reply_to_id on the entry status | +| 6450 | Comment to comment exports with in_reply_to_id on the parent reply | +| 6456 | Entry and comment exports include the configured Mastodon language code | +| 6475 | Deletion sync waits at least five minutes after a completed content sync | +| 6527 | Create and update status requests include the configured Mastodon language code | +| 6570 | Initial Mastodon media uploads include the attachment description in POST /api/v2/media | +| 6602 | Unchanged entry media is reused without a new upload when only the post text changes | +| 6675 | Mastodon 4.1+ updates changed image descriptions through status media_attributes without re-uploading media | +| 6712 | Older Mastodon versions fall back to a fresh upload when only the media description changes | +| 6799 | Full local-to-remote sync reuses stored media IDs when the attachments did not change | +| 6899 | Full sync updates changed media descriptions through status media_attributes on Mastodon 4.1+ | +| 7022 | Batch entry export keeps older FlatPress entries below newer ones on Mastodon | +| 7125 | New local comment on an already synchronized older entry is exported to Mastodon | +| 7204 | Disabled comment/reply sync keeps local comments off Mastodon | +| 7283 | Disabled comment/reply sync skips remote reply deletion follow-up | +| 7379 | Known synchronized entry mappings older than the sync start date do not trigger context refreshes | +| 7453 | Scheduled content synchronization respects the automatic recent-content window | +| 7479 | Productive content-sync workset loads only active scheduled comment shards | +| 7538 | Normal manual synchronization bypasses the daily due check but still respects the automatic window | +| 7600 | Explicit full manual synchronization bypasses the automatic window while keeping normal limits | +| 7679 | Older changed mapped FlatPress entries are synchronized through the dirty queue | +| 7731 | Post-success comment hook queues older changed mapped comments | +| 7761 | Scheduled sync updates older dirty comments through direct YY/MM dirty-parent candidates | +| 7808 | Remote-write guard suppresses dirty tracking for Mastodon-owned entry_save calls | +| 7897 | Large scheduled dirty-tracking sync parses only active-window and dirty entries | +| 7980 | Optional old-thread reply checks rotate through known synchronized threads | +| 8021 | Disabled old-thread reply checks do not refresh known synchronized entry contexts | +| 8102 | Disabled comment/reply sync imports Mastodon entries without fetching reply contexts or notifications | +| 8181 | Automatic scheduled synchronization rotates known synchronized threads through the full sync path | +| 8267 | Notification hints import a new reply on an old mapped Mastodon entry without context rotation | +| 8283 | Notification fallback payloads are ignored when a normal mention status is available | +| 8352 | Notification hints import a new reply on an old mapped Mastodon reply with reply metadata | +| 8457 | Notification context hints use the old-thread budget before normal rotation | +| 8508 | Reply-notification polling is skipped until read:notifications is authorized | +| 8631 | Remote updates do not overwrite existing local content when the toggle is disabled | +| 8651 | Remote updates overwrite existing local content when the toggle is enabled | +| 8676 | FlatPress single-image entry exports Mastodon media_ids and strips raw image BBCode | +| 8687 | FlatPress gallery entry exports Mastodon media_ids up to the instance limit and strips raw gallery BBCode | +| 8825 | Sync start date filters local exports by entry and comment date | +| 8911 | Sync start date filters remote imports by status and reply date | +| 9003 | Remote Mastodon imports honor the FlatPress timeoffset for stored entry/comment dates and ordering keys | +| 9074 | Remote sync start filtering respects the FlatPress timeoffset near midnight | +| 9139 | Sync reports local export failures instead of silently succeeding | +| 9157 | Shared-request synchronization refreshes the PHP execution budget for long Mastodon work | +| 9240 | Synchronized local comments are not imported as duplicate entries from Mastodon by default | +| 9332 | Synchronized local comments may be imported as entries when the toggle is enabled | +| 9467 | Local FlatPress comments on imported Mastodon entries sync back to Mastodon, including nested replies | +| 9543 | Asynchronous Mastodon media uploads are polled until the attachment is ready | +| 9567 | Long-running Mastodon media uploads refresh the PHP execution budget for upload and polling | +| 9639 | Slow asynchronous Mastodon video uploads keep polling beyond the old short retry window | +| 9705 | Mastodon audio uploads keep polling even when pending responses have no preview_url | +| 9770 | Follow-up deletion sync removes remote Mastodon content after local FlatPress deletion | +| 9795 | Follow-up deletion sync updates last_deletion_run without overwriting the last synchronization timestamp or content counters | +| 9851 | Deletion sync falls back to DELETE without delete_media for older Mastodon servers | +| 9914 | Follow-up deletion sync removes mirrored FlatPress content after remote Mastodon deletion | +| 9969 | Deletion sync skips old locally deleted mappings outside the sync start date window | +| 10019 | Deletion sync skips old remotely mirrored mappings outside the sync start date window | +| 10076 | Scheduled deletion sync skips remote lookups outside the automatic scheduled window | +| 10149 | Scheduled deletion sync still propagates old local deletions outside the automatic window | +| 10210 | Large scheduled deletion syncs resume from the saved entry cursor | +| 10294 | Large scheduled deletion syncs resume from the saved comment cursor inside one entry shard | +| 10339 | Disabling deletion synchronization clears pending delete work without issuing deletion requests | +| 10359 | Content sync before deletion sync protects a locally deleted exported FlatPress comment from stale re-import | +| 10378 | Deletion sync after the protected content sync reattaches the imported descendant reply to the synchronized entry status and keeps a later verification pending | +| 10421 | Normal manual partial content sync keeps an old locally deleted exported comment tombstoned without stale re-import | +| 10460 | Automatic scheduled partial content sync keeps an old locally deleted exported comment tombstoned without stale re-import | +| 10497 | Remote import defensively tombstones a missing source=local comment when the deletion hook was bypassed | +| 10524 | Comment mapping rejects duplicate remote ids and removes the previous reverse id on a legitimate remap | +| 10551 | Deletion sync keeps imported descendants for a later follow-up verification when Mastodon still returns the child reply once | +| 10568 | Tombstones prevent re-importing the deleted exported parent comment while descendant rechecks are still pending | +| 10581 | A later follow-up deletion sync removes the imported descendant once Mastodon reports the child reply missing | +| 10612 | Content sync before deletion sync also protects a locally deleted exported FlatPress comment when the imported descendant reply already has its own child | +| 10632 | Deletion sync after the protected content sync keeps the descendant child below the reattached reply and verifies both remote replies in the same pass | +| 10672 | Deletion sync removes imported descendants immediately when Mastodon already reports the child reply missing in the first follow-up pass | +| 10703 | Hybrid descendant rechecks keep only the direct child queued after the first stale follow-up pass of a deeper reply chain | +| 10722 | Hybrid descendant rechecks remove the direct child and its direct child within the same targeted follow-up request | +| 10747 | Locally deleted imported external Mastodon reply is tombstoned immediately and not re-imported unchanged | +| 10772 | Locally deleted imported external Mastodon reply is not re-imported after the remote author edits it | +| 10795 | Deletion sync treats locally deleted imported remote replies as local ignore decisions without remote DELETE | +| 10877 | Remote Mastodon comment updates increment the local-comment update counter instead of the entry counter | +| 10918 | Admin assignment exposes split sync counters, local timestamps, old-thread and deletion-sync options | +| 11011 | Admin maintenance actions are separated into a dedicated template reached from the main plugin page | +| 11102 | Media upload cleanup deletes already uploaded attachments when a later media upload fails | +| 11169 | Media upload cleanup deletes uploaded attachments when final status creation fails | +| 11227 | OAuth scope discovery prefers the profile scope on current Mastodon instances | +| 11273 | OAuth scope discovery falls back to read:accounts plus notifications on older Mastodon instances | +| 11325 | Existing registered apps keep the legacy read:accounts scope until they are re-registered | +| 11416 | Instance information refresh persists a compact snapshot including the exact Mastodon version | +| 11462 | Admin assignment exposes cached instance-information rows without triggering another live instance request | +| 11479 | Changing the configured instance URL invalidates the saved instance-information snapshot | +| 11517 | Sync-related instance limit helpers reuse the stored instance snapshot without another /api/v2/instance request | +| 11539 | Manual sync exports FlatPress comments on remote-sourced entries to Mastodon replies | +| 11557 | Replies to FlatPress comments on remote-sourced entries are exported as replies to the parent Mastodon reply | +| 11587 | Unsynchronized local parent comment is exported before local child reply | +| 11607 | Non-forced sync exports FlatPress comments on remote-sourced entries to Mastodon replies | +| 11630 | Manual sync exports Emoticons plugin shortcodes from FlatPress entries to Unicode emoji in Mastodon status requests | +| 11646 | Manual sync exports Emoticons plugin shortcodes from FlatPress comments to Unicode emoji in Mastodon reply requests | +| 11667 | Manual sync imports another Mastodon member reply to an exported FlatPress comment only as a FlatPress comment when comment-as-entry import is disabled | +| 11693 | Non-forced sync imports another Mastodon member reply to an exported FlatPress comment only as a FlatPress comment when comment-as-entry import is disabled | +| 11716 | Another Mastodon member reply to an exported FlatPress comment is still imported as a FlatPress comment when comment-as-entry import is enabled | +| 11733 | Disabling the quote option imports a Mastodon reply to an exported FlatPress comment without a leading quote block | +| 11747 | Imported Mastodon replies to another Mastodon reply quote the replied-to Mastodon user and text by default | +| 11766 | Disabling the quote option imports Mastodon reply-to-reply comments without a leading quote block | +| 11780 | Manual sync imports a Mastodon self-reply to an exported FlatPress comment only as a FlatPress comment | +| 11804 | Non-forced sync imports a Mastodon self-reply to an exported FlatPress comment only as a FlatPress comment | +| 11857 | Scheduler state is written as a compact summary without full mapping arrays | +| 11876 | Fresh scheduler-state read uses the APCu-capable FlatPress I/O path and does not load full state.json | +| 11905 | Stale scheduler-state falls back to the full state and rebuilds the summary | +| 11926 | Manual admin synchronization still loads the full state before reporting configuration errors | +| 11945 | sync.log uses append-only writes with size-based rotation | +| 11971 | Large skip volumes are logged as aggregate summaries | +| 11984 | Small 300x10 state keeps scheduler-state compact and disables full APCu fallback | +| 12006 | Fresh small scheduler-state read avoids full state.json and uses APCu-capable file I/O | +| 12029 | Large 3000x10 state keeps scheduler-state compact and disables full APCu fallback | +| 12052 | Fresh large scheduler-state read avoids full state.json and uses APCu-capable file I/O | +| 12168 | Automatic scheduled sync exports a new current comment on an old mapped entry through dirty_comments | +| 12271 | Authenticated FlatPress comments export after a stored LOGGEDIN marker without visitor opt-in | +| 12382 | Admin session does not grant Mastodon export to visitor comments without opt-in | +| 12448 | Visitor Mastodon opt-in checkbox preserves POST state and hook priorities/signatures are explicit | +| 12550 | Visitor opt-in persists through the direct FlatPress save path when CommentCenter is disabled | +| 12729 | CommentCenter preserves visitor Mastodon opt-in until admin approval and export | +| 12867 | CommentCenter rejection and bounded pruning remove orphaned pending opt-in grants | +| 12973 | Visitor opt-in is stored without complete credentials and later permits comment export | +| 13078 | Opted-in visitor replies export even when their local parent has no Mastodon opt-in | +| 13139 | One-way mode option is disabled by default, normalized, assigned to admin UI, templated and translated | +| 13179 | One-way admin save preserves hidden import options while keeping one-way mode enabled | +| 13196 | One-way admin save preserves hidden import options when one-way mode is disabled in the same submit | +| 13216 | Bidirectional admin save still stores visible unchecked import options as disabled | +| 13248 | One-way admin template hides import controls, notification hints and import-only counters | +| 13274 | One-way admin template keeps export, OAuth, instance, token, state and deletion outputs visible | +| 13290 | One-way admin assignment hides import-only companion plugins and uses one-way companion intro | +| 13396 | One-way mode blocks Mastodon-to-FlatPress imports while FlatPress-to-Mastodon export still runs | +| 13483 | Automatic one-way content sync refreshes the Mastodon widget profile cache without remote import reads | +| 13558 | One-way deletion sync keeps local content, removes stale remote mappings and queues re-export | +| 13633 | One-way content sync re-exports local objects whose remote mappings were unlinked after remote deletion | +| 13702 | One-way pending descendant rechecks keep local comments and queue them for re-export instead of deleting them | +| 13723 | Mastodon widget remains hidden and performs no HTTP requests while its local profile cache is missing | +| 13766 | Mastodon widget profile cache is refreshed from verify_credentials and stores only public profile data plus a local avatar | +| 13791 | Mastodon widget renders compact local-cache markup without remote API calls or inline CSS | +| 13815 | Mastodon widget stylesheet is loaded by plugin_mastodon_head as a versioned CSS asset | +| 13830 | Mastodon widget profile refresh reuses the cached avatar when the Mastodon avatar URL is unchanged | +| 13857 | Mastodon widget uses a localized fallback avatar alt text for Mastodon 4.0-4.5 account payloads without avatar_description | +| 13876 | Mastodon widget hides incomplete local profile caches instead of falling back to remote avatar URLs | +| 13901 | Mastodon widget is registered and translated in all FlatPress plugin language files | diff --git a/fp-plugins/mastodon/developer-docs/06-Process-Flow.md b/fp-plugins/mastodon/developer-docs/06-Process-Flow.md index 1a16e0d4..2f942669 100644 --- a/fp-plugins/mastodon/developer-docs/06-Process-Flow.md +++ b/fp-plugins/mastodon/developer-docs/06-Process-Flow.md @@ -664,7 +664,7 @@ flowchart TD ### 2.1 Local-to-remote status-text pipeline The status-text builder intentionally keeps Mastodon-visible length calculation separate from -FlatPress storage. Mastodon counts each URL as the instance's configured URL budget. +FlatPress storage. Mastodon counts each recognized URL as the instance's configured URL budget, while punctuation immediately after the URL remains normal text. ```mermaid flowchart LR @@ -678,12 +678,13 @@ flowchart LR Footer["Build plugin-owned hashtag footer"] Join["Join body and footer"] InstanceLimits["Load /api/v2/instance limits
max_characters and URL reserved length"] + UrlSpans["plugin_mastodon_status_text_url_spans
trim punctuation boundaries"] Budget["plugin_mastodon_status_text_length"] Truncate["plugin_mastodon_limit_status_text"] Result["Mastodon status text"] Source --> StripMeta --> BBCode --> MediaRefs --> Links --> Emoji --> Tags --> Footer --> Join - Join --> InstanceLimits --> Budget + Join --> InstanceLimits --> UrlSpans --> Budget Budget --> Truncate --> Result ``` diff --git a/fp-plugins/mastodon/developer-docs/07-Function-Organigram.md b/fp-plugins/mastodon/developer-docs/07-Function-Organigram.md index 90dec9ef..9a254ada 100644 --- a/fp-plugins/mastodon/developer-docs/07-Function-Organigram.md +++ b/fp-plugins/mastodon/developer-docs/07-Function-Organigram.md @@ -9,9 +9,9 @@ The file intentionally combines two views: - a human-oriented organigram that explains responsibilities, call paths and maintenance rules; - a generated function catalog with current line references that is checked against `fp-plugins/mastodon/plugin.mastodon.php`. -The plugin currently contains **455** callable functions/methods. +The plugin currently contains **456** callable functions/methods. -- **452** top-level plugin functions +- **453** top-level plugin functions - **6** admin action methods implemented with the shared method names `setup()`, `main()` or `onsubmit()` ## Function count @@ -95,7 +95,7 @@ flowchart LR State --> Write[atomic shard/main-state write with last_error support] ``` -The export path is expected to behave the same for users as before the split-state change: changed FlatPress entries and comments are posted or updated on Mastodon. The implementation difference is that comment mappings are loaded entry-by-entry instead of as one monolithic `comments` array. +The export path is expected to behave the same for users as before the split-state change: changed FlatPress entries and comments are posted or updated on Mastodon. The implementation difference is that comment mappings are loaded entry-by-entry instead of as one monolithic `comments` array. Status text budgeting now uses `plugin_mastodon_status_text_url_spans()` before `plugin_mastodon_status_text_length()` and `plugin_mastodon_limit_status_text()`, so sentence punctuation directly after a URL is counted as normal text instead of being hidden inside the fixed Mastodon URL placeholder. ### Remote → local import path @@ -180,27 +180,27 @@ This group contains the functions and shared admin action methods that FlatPress The hidden `mastodon_maintenance` panel must have its language/panelstrings alias in every language file before FlatPress builds `$panelstrings`; the runtime fallback remains only the second safety layer. - `plugin_mastodon_head()` — line 2374 — Print Mastodon profile metadata into the HTML head. -- `plugin_mastodon_widget()` — line 11733 — Render the compact Mastodon profile widget from local cache only. +- `plugin_mastodon_widget()` — line 11787 — Render the compact Mastodon profile widget from local cache only. - `plugin_mastodon_local_comment_from_authenticated_author()` — line 2797 — Detect authenticated FlatPress/admin comments that do not require the public visitor opt-in marker. - `plugin_mastodon_optin_comment_to_reply()` — line 2702 — Render the optional public comment-form Mastodon/Fediverse opt-in checkbox. - `plugin_mastodon_on_commentcenter_comment_logged()` — line 2727 — Store a frontend opt-in state grant when CommentCenter holds a visitor comment for moderation instead of saving it immediately. - `plugin_mastodon_on_commentcenter_comment_discarded()` — line 2766 — Remove or finalize a pending opt-in grant when CommentCenter permanently discards its pending comment. - `plugin_mastodon_admin_language_strings()` — line 6818 — Return Mastodon admin-language strings with guaranteed maintenance fallbacks. - `plugin_mastodon_admin_apply_save_post()` — line 2029 — Apply submitted admin settings while preserving hidden remote-import options. -- `plugin_mastodon_sync_due()` — line 13626 — Determine whether the scheduled synchronization is currently due using stored UTC sync time and UTC `last_run` days. -- `plugin_mastodon_maybe_sync()` — line 13749 — Run the scheduled synchronization when the current request is due. -- `plugin_mastodon_admin_boolean_label()` — line 13788 — Return a localized yes/no/unknown label for admin diagnostics. -- `plugin_mastodon_admin_add_info_row()` — line 13803 — Add one admin diagnostics row when the value is available. -- `plugin_mastodon_instance_registration_summary()` — line 13824 — Summarize the registration state advertised by the Mastodon instance. -- `plugin_mastodon_admin_instance_info_rows()` — line 13850 — Build the rows for the admin instance-information diagnostics table. -- `plugin_mastodon_admin_panel_link()` — line 13922 — Build a local admin-panel link for Mastodon plugin pages. -- `plugin_mastodon_admin_empty_state_maintenance_result()` — line 13942 — Return an empty admin maintenance result container. -- `plugin_mastodon_admin_state_maintenance_result()` — line 13958 — Convert comment-shard diagnostics into template-friendly admin rows. -- `plugin_mastodon_cli_comment_shard_maintenance()` — line 13995 — Print command-line comment-shard diagnostics or perform a repair. -- `plugin_mastodon_admin_assign()` — line 14029 — Assign plugin data to Smarty for the admin panel. -- `setup()` — line 14166 — Register the active Mastodon admin template and assign plugin data to Smarty. -- `main()` — line 14171 — Keep the FlatPress admin panel lifecycle stable without additional processing. -- `onsubmit()` — line 14175 — Process submitted Mastodon admin actions for the active admin panel. +- `plugin_mastodon_sync_due()` — line 13680 — Determine whether the scheduled synchronization is currently due using stored UTC sync time and UTC `last_run` days. +- `plugin_mastodon_maybe_sync()` — line 13803 — Run the scheduled synchronization when the current request is due. +- `plugin_mastodon_admin_boolean_label()` — line 13842 — Return a localized yes/no/unknown label for admin diagnostics. +- `plugin_mastodon_admin_add_info_row()` — line 13857 — Add one admin diagnostics row when the value is available. +- `plugin_mastodon_instance_registration_summary()` — line 13878 — Summarize the registration state advertised by the Mastodon instance. +- `plugin_mastodon_admin_instance_info_rows()` — line 13904 — Build the rows for the admin instance-information diagnostics table. +- `plugin_mastodon_admin_panel_link()` — line 13976 — Build a local admin-panel link for Mastodon plugin pages. +- `plugin_mastodon_admin_empty_state_maintenance_result()` — line 13996 — Return an empty admin maintenance result container. +- `plugin_mastodon_admin_state_maintenance_result()` — line 14012 — Convert comment-shard diagnostics into template-friendly admin rows. +- `plugin_mastodon_cli_comment_shard_maintenance()` — line 14049 — Print command-line comment-shard diagnostics or perform a repair. +- `plugin_mastodon_admin_assign()` — line 14083 — Assign plugin data to Smarty for the admin panel. +- `setup()` — line 14220 — Register the active Mastodon admin template and assign plugin data to Smarty. +- `main()` — line 14225 — Keep the FlatPress admin panel lifecycle stable without additional processing. +- `onsubmit()` — line 14229 — Process submitted Mastodon admin actions for the active admin panel. ## B. Defaults, configuration, secrets, and centralized FlatPress feature toggles @@ -280,22 +280,22 @@ This group owns normalized option values, stored secrets, instance snapshots, OA This group owns request-local caches, FlatPress file I/O wrappers, log rotation, scheduler summaries, sync cooldown guards, state migration, per-entry comment shards, repair/diagnosis, dirty queues, tombstones, pending rechecks and durable state-write error reporting. -- `plugin_mastodon_ensure_profile_dir()` — line 11270 — Ensure the public profile-widget cache directory and directory-listing guard exist. -- `plugin_mastodon_widget_lang_string()` — line 11290 — Return localized public Mastodon widget strings with safe fallbacks. -- `plugin_mastodon_profile_text()` — line 11312 — Normalize public account text before writing it to the profile-widget cache. -- `plugin_mastodon_profile_public_url_allowed()` — line 11327 — Validate public HTTP(S) profile and avatar URLs before storing them. -- `plugin_mastodon_profile_relative_to_absolute()` — line 11345 — Resolve a profile-cache path below `fp-content/plugin_mastodon/profile/` to a local filesystem path. -- `plugin_mastodon_profile_relative_to_url()` — line 11362 — Resolve a profile-cache path below `fp-content/plugin_mastodon/profile/` to a public FlatPress URL. -- `plugin_mastodon_profile_cache_read()` — line 11379 — Read and validate the local public profile-widget cache. -- `plugin_mastodon_profile_cache_write()` — line 11420 — Write the public profile-widget cache without storing secrets. -- `plugin_mastodon_profile_url_from_account()` — line 11438 — Choose the Mastodon profile URL from account data or the configured instance/username. -- `plugin_mastodon_profile_avatar_url_from_account()` — line 11451 — Choose the preferred Mastodon avatar URL, preferring `avatar_static`. -- `plugin_mastodon_profile_avatar_type()` — line 11467 — Detect and allow only supported avatar image MIME types. -- `plugin_mastodon_profile_download_avatar()` — line 11518 — Download, validate and atomically store the avatar in the local profile cache directory. -- `plugin_mastodon_refresh_profile_cache_from_account()` — line 11579 — Build the public profile-widget cache from a verified Mastodon account payload. -- `plugin_mastodon_refresh_profile_cache()` — line 11667 — Refresh the public profile-widget cache through `verify_credentials`. -- `plugin_mastodon_refresh_profile_cache_for_sync()` — line 11686 — Refresh the public profile-widget cache during content synchronization without deciding the sync outcome by itself. -- `plugin_mastodon_widget_avatar_alt()` — line 11712 — Build the localized avatar alt text for the public Mastodon widget. +- `plugin_mastodon_ensure_profile_dir()` — line 11324 — Ensure the public profile-widget cache directory and directory-listing guard exist. +- `plugin_mastodon_widget_lang_string()` — line 11344 — Return localized public Mastodon widget strings with safe fallbacks. +- `plugin_mastodon_profile_text()` — line 11366 — Normalize public account text before writing it to the profile-widget cache. +- `plugin_mastodon_profile_public_url_allowed()` — line 11381 — Validate public HTTP(S) profile and avatar URLs before storing them. +- `plugin_mastodon_profile_relative_to_absolute()` — line 11399 — Resolve a profile-cache path below `fp-content/plugin_mastodon/profile/` to a local filesystem path. +- `plugin_mastodon_profile_relative_to_url()` — line 11416 — Resolve a profile-cache path below `fp-content/plugin_mastodon/profile/` to a public FlatPress URL. +- `plugin_mastodon_profile_cache_read()` — line 11433 — Read and validate the local public profile-widget cache. +- `plugin_mastodon_profile_cache_write()` — line 11474 — Write the public profile-widget cache without storing secrets. +- `plugin_mastodon_profile_url_from_account()` — line 11492 — Choose the Mastodon profile URL from account data or the configured instance/username. +- `plugin_mastodon_profile_avatar_url_from_account()` — line 11505 — Choose the preferred Mastodon avatar URL, preferring `avatar_static`. +- `plugin_mastodon_profile_avatar_type()` — line 11521 — Detect and allow only supported avatar image MIME types. +- `plugin_mastodon_profile_download_avatar()` — line 11572 — Download, validate and atomically store the avatar in the local profile cache directory. +- `plugin_mastodon_refresh_profile_cache_from_account()` — line 11633 — Build the public profile-widget cache from a verified Mastodon account payload. +- `plugin_mastodon_refresh_profile_cache()` — line 11721 — Refresh the public profile-widget cache through `verify_credentials`. +- `plugin_mastodon_refresh_profile_cache_for_sync()` — line 11740 — Refresh the public profile-widget cache during content synchronization without deciding the sync outcome by itself. +- `plugin_mastodon_widget_avatar_alt()` — line 11766 — Build the localized avatar alt text for the public Mastodon widget. - `plugin_mastodon_runtime_cache_get()` — line 809 — Return a value from the request-local plugin cache. - `plugin_mastodon_runtime_cache_set()` — line 833 — Store a value in the request-local plugin cache. - `plugin_mastodon_runtime_cache_clear()` — line 851 — Clear one request-local plugin cache bucket or the complete cache. @@ -547,25 +547,25 @@ This group reads local entries/comments, handles local and remote media descript - `plugin_mastodon_media_download()` — line 9143 — Download a remote media asset. - `plugin_mastodon_remote_download_basename()` — line 9157 — Build a safe basename for a downloaded remote attachment. - `plugin_mastodon_build_imported_media_bbcode()` — line 9190 — Build FlatPress BBCode for imported remote media attachments. -- `plugin_mastodon_is_two_digit_path_segment()` — line 10316 — Determine whether a FlatPress content-tree path segment is a two-digit date segment. -- `plugin_mastodon_two_digit_year_to_full_year()` — line 10325 — Convert a FlatPress two-digit year segment into the full year used by PHP's legacy date parser. -- `plugin_mastodon_entry_month_end_date_key()` — line 10336 — Return the last date key that can occur inside a FlatPress YY/MM content directory. -- `plugin_mastodon_entry_month_may_match_scheduled_window()` — line 10358 — Determine whether a FlatPress YY/MM directory can contain scheduled-sync entry candidates. -- `plugin_mastodon_collect_entry_files_from_month()` — line 10378 — Collect direct entry files from one canonical FlatPress YY/MM content directory. -- `plugin_mastodon_collect_entry_files_legacy()` — line 10411 — Legacy fallback for non-canonical content trees. -- `plugin_mastodon_collect_entry_files()` — line 10439 — Collect canonical entry files directly from FlatPress YY/MM content directories. -- `plugin_mastodon_collect_scheduled_entry_files()` — line 10482 — Collect scheduled-sync entry candidates directly from relevant FlatPress YY/MM content directories. -- `plugin_mastodon_add_dirty_entry_files()` — line 10527 — Append existing dirty entry files by canonical FlatPress ID. -- `plugin_mastodon_collect_entry_files_for_sync()` — line 10547 — Collect local entry files for a local-to-remote synchronization pass; scheduled runs add all dirty parents without a hard cap. -- `plugin_mastodon_local_item_timestamp()` — line 10566 — Resolve the best timestamp for a local FlatPress item. -- `plugin_mastodon_compare_local_entries_for_export()` — line 10595 — Compare local FlatPress entries for Mastodon export order. -- `plugin_mastodon_test_note_local_entry_parse()` — line 10614 — Increment a simulation-only local-entry parse counter when enabled. -- `plugin_mastodon_dirty_entry_id_lookup()` — line 10626 — Return entry identifiers that are explicitly queued by dirty entry/comment hooks. -- `plugin_mastodon_content_sync_comment_entry_ids()` — line 10656 — Return entry ids whose comment shards may be needed by the current content-sync workset. -- `plugin_mastodon_state_load_content_sync_comment_workset()` — line 10683 — Load comment shards needed for the current content synchronization workset. -- `plugin_mastodon_should_parse_local_entry_for_sync()` — line 10699 — Determine whether a local entry file should be parsed during a scheduled local-to-remote pass. -- `plugin_mastodon_list_local_entries_for_sync()` — line 10724 — List local FlatPress entries for a local-to-remote synchronization pass. -- `plugin_mastodon_list_local_entries()` — line 10763 — List local FlatPress entries ordered for export. +- `plugin_mastodon_is_two_digit_path_segment()` — line 10370 — Determine whether a FlatPress content-tree path segment is a two-digit date segment. +- `plugin_mastodon_two_digit_year_to_full_year()` — line 10379 — Convert a FlatPress two-digit year segment into the full year used by PHP's legacy date parser. +- `plugin_mastodon_entry_month_end_date_key()` — line 10390 — Return the last date key that can occur inside a FlatPress YY/MM content directory. +- `plugin_mastodon_entry_month_may_match_scheduled_window()` — line 10412 — Determine whether a FlatPress YY/MM directory can contain scheduled-sync entry candidates. +- `plugin_mastodon_collect_entry_files_from_month()` — line 10432 — Collect direct entry files from one canonical FlatPress YY/MM content directory. +- `plugin_mastodon_collect_entry_files_legacy()` — line 10465 — Legacy fallback for non-canonical content trees. +- `plugin_mastodon_collect_entry_files()` — line 10493 — Collect canonical entry files directly from FlatPress YY/MM content directories. +- `plugin_mastodon_collect_scheduled_entry_files()` — line 10536 — Collect scheduled-sync entry candidates directly from relevant FlatPress YY/MM content directories. +- `plugin_mastodon_add_dirty_entry_files()` — line 10581 — Append existing dirty entry files by canonical FlatPress ID. +- `plugin_mastodon_collect_entry_files_for_sync()` — line 10601 — Collect local entry files for a local-to-remote synchronization pass; scheduled runs add all dirty parents without a hard cap. +- `plugin_mastodon_local_item_timestamp()` — line 10620 — Resolve the best timestamp for a local FlatPress item. +- `plugin_mastodon_compare_local_entries_for_export()` — line 10649 — Compare local FlatPress entries for Mastodon export order. +- `plugin_mastodon_test_note_local_entry_parse()` — line 10668 — Increment a simulation-only local-entry parse counter when enabled. +- `plugin_mastodon_dirty_entry_id_lookup()` — line 10680 — Return entry identifiers that are explicitly queued by dirty entry/comment hooks. +- `plugin_mastodon_content_sync_comment_entry_ids()` — line 10710 — Return entry ids whose comment shards may be needed by the current content-sync workset. +- `plugin_mastodon_state_load_content_sync_comment_workset()` — line 10737 — Load comment shards needed for the current content synchronization workset. +- `plugin_mastodon_should_parse_local_entry_for_sync()` — line 10753 — Determine whether a local entry file should be parsed during a scheduled local-to-remote pass. +- `plugin_mastodon_list_local_entries_for_sync()` — line 10778 — List local FlatPress entries for a local-to-remote synchronization pass. +- `plugin_mastodon_list_local_entries()` — line 10817 — List local FlatPress entries ordered for export. ## G. HTTP transport, PHP timeout budgeting, instance capability lookup, status-length budgeting, OAuth, Mastodon API calls, and media upload @@ -605,60 +605,61 @@ This group performs network-facing work: request budgeting, persistent rate-limi - `plugin_mastodon_instance_media_limit()` — line 9659 — Return the media attachment limit of the configured instance. - `plugin_mastodon_instance_media_description_limit()` — line 9672 — Return the media description length limit of the configured instance. - `plugin_mastodon_instance_url_reserved_length()` — line 9685 — Return the per-URL character budget used by the configured instance. -- `plugin_mastodon_status_text_length()` — line 9699 — Calculate the Mastodon-visible length of a plain-text status. -- `plugin_mastodon_limit_status_text()` — line 9731 — Limit Mastodon plain text while respecting the instance URL budget. -- `plugin_mastodon_http_request_multipart()` — line 9810 — Perform a multipart HTTP request. -- `plugin_mastodon_fetch_media_attachment()` — line 9943 — Fetch the current processing status of a Mastodon media attachment. -- `plugin_mastodon_delete_media_attachment()` — line 9953 — Delete an uploaded Mastodon media attachment before it is attached to a final status. -- `plugin_mastodon_cleanup_uploaded_media()` — line 9976 — Best-effort cleanup for uploaded Mastodon media that never reached a final status request. -- `plugin_mastodon_media_processing_attempts()` — line 10018 — Determine how patiently the plugin should wait for Mastodon media processing. -- `plugin_mastodon_media_transfer_timeout()` — line 10042 — Determine a practical transfer timeout for one media upload. -- `plugin_mastodon_wait_for_media_attachment()` — line 10062 — Wait briefly until an asynchronously uploaded Mastodon media attachment is ready. -- `plugin_mastodon_upload_media_items()` — line 10111 — Upload local media items to Mastodon and collect the created media IDs. -- `plugin_mastodon_prepare_entry_media_sync_plan()` — line 10228 — Decide whether a local entry update can reuse already-uploaded Mastodon media or needs a fresh upload. -- `plugin_mastodon_parse_http_response_headers()` — line 10801 — Parse raw HTTP response headers. -- `plugin_mastodon_stream_context_request()` — line 10831 — Perform an HTTP request through a stream context fallback. -- `plugin_mastodon_array_is_list()` — line 10871 — Detect whether a value is a numerically indexed list. -- `plugin_mastodon_array_contains_only_form_scalars()` — line 10891 — Detect whether a list contains only scalar-compatible form values. -- `plugin_mastodon_http_build_query()` — line 10909 — Build an application/x-www-form-urlencoded query string for Mastodon requests. -- `plugin_mastodon_http_request()` — line 10965 — Perform an HTTP request using cURL or the stream fallback. -- `plugin_mastodon_mastodon_api()` — line 11084 — Call the Mastodon API and return the raw HTTP response. -- `plugin_mastodon_mastodon_json()` — line 11134 — Call the Mastodon API and decode a JSON response. -- `plugin_mastodon_response_error_message()` — line 11149 — Extract the most useful error message from an API response. -- `plugin_mastodon_register_app()` — line 11178 — Register the FlatPress application on the configured Mastodon instance. -- `plugin_mastodon_build_authorize_url()` — line 11201 — Build the OAuth authorization URL. -- `plugin_mastodon_exchange_code_for_token()` — line 11221 — Exchange an OAuth authorization code for an access token. -- `plugin_mastodon_verify_credentials()` — line 11251 — Verify the currently configured access token. -- `plugin_mastodon_instance_character_limit()` — line 11778 — Return the status character limit of the configured instance. -- `plugin_mastodon_fetch_account_statuses()` — line 11793 — Fetch statuses for the authenticated Mastodon account. -- `plugin_mastodon_fetch_status_context()` — line 11851 — Fetch the conversation context for a Mastodon status. -- `plugin_mastodon_fetch_status()` — line 11885 — Fetch a single Mastodon status. -- `plugin_mastodon_delete_status()` — line 11896 — Delete a Mastodon status. -- `plugin_mastodon_delete_status_should_retry_without_delete_media()` — line 11924 — Check whether a failed status deletion may be caused by Mastodon versions before 4.4.0 not understanding the optional delete_media query parameter. -- `plugin_mastodon_status_missing_response()` — line 11944 — Check whether an API response means that the referenced Mastodon status is missing. -- `plugin_mastodon_create_status()` — line 11957 — Create a Mastodon status. -- `plugin_mastodon_update_status()` — line 11984 — Update an existing Mastodon status. +- `plugin_mastodon_status_text_url_spans()` — line 9703 — Extract Mastodon-countable URL spans and leave trailing sentence punctuation outside the URL placeholder budget. +- `plugin_mastodon_status_text_length()` — line 9751 — Calculate the Mastodon-visible length of a plain-text status. +- `plugin_mastodon_limit_status_text()` — line 9784 — Limit Mastodon plain text while respecting the instance URL budget. +- `plugin_mastodon_http_request_multipart()` — line 9864 — Perform a multipart HTTP request. +- `plugin_mastodon_fetch_media_attachment()` — line 9997 — Fetch the current processing status of a Mastodon media attachment. +- `plugin_mastodon_delete_media_attachment()` — line 10007 — Delete an uploaded Mastodon media attachment before it is attached to a final status. +- `plugin_mastodon_cleanup_uploaded_media()` — line 10030 — Best-effort cleanup for uploaded Mastodon media that never reached a final status request. +- `plugin_mastodon_media_processing_attempts()` — line 10072 — Determine how patiently the plugin should wait for Mastodon media processing. +- `plugin_mastodon_media_transfer_timeout()` — line 10096 — Determine a practical transfer timeout for one media upload. +- `plugin_mastodon_wait_for_media_attachment()` — line 10116 — Wait briefly until an asynchronously uploaded Mastodon media attachment is ready. +- `plugin_mastodon_upload_media_items()` — line 10165 — Upload local media items to Mastodon and collect the created media IDs. +- `plugin_mastodon_prepare_entry_media_sync_plan()` — line 10282 — Decide whether a local entry update can reuse already-uploaded Mastodon media or needs a fresh upload. +- `plugin_mastodon_parse_http_response_headers()` — line 10855 — Parse raw HTTP response headers. +- `plugin_mastodon_stream_context_request()` — line 10885 — Perform an HTTP request through a stream context fallback. +- `plugin_mastodon_array_is_list()` — line 10925 — Detect whether a value is a numerically indexed list. +- `plugin_mastodon_array_contains_only_form_scalars()` — line 10945 — Detect whether a list contains only scalar-compatible form values. +- `plugin_mastodon_http_build_query()` — line 10963 — Build an application/x-www-form-urlencoded query string for Mastodon requests. +- `plugin_mastodon_http_request()` — line 11019 — Perform an HTTP request using cURL or the stream fallback. +- `plugin_mastodon_mastodon_api()` — line 11138 — Call the Mastodon API and return the raw HTTP response. +- `plugin_mastodon_mastodon_json()` — line 11188 — Call the Mastodon API and decode a JSON response. +- `plugin_mastodon_response_error_message()` — line 11203 — Extract the most useful error message from an API response. +- `plugin_mastodon_register_app()` — line 11232 — Register the FlatPress application on the configured Mastodon instance. +- `plugin_mastodon_build_authorize_url()` — line 11255 — Build the OAuth authorization URL. +- `plugin_mastodon_exchange_code_for_token()` — line 11275 — Exchange an OAuth authorization code for an access token. +- `plugin_mastodon_verify_credentials()` — line 11305 — Verify the currently configured access token. +- `plugin_mastodon_instance_character_limit()` — line 11832 — Return the status character limit of the configured instance. +- `plugin_mastodon_fetch_account_statuses()` — line 11847 — Fetch statuses for the authenticated Mastodon account. +- `plugin_mastodon_fetch_status_context()` — line 11905 — Fetch the conversation context for a Mastodon status. +- `plugin_mastodon_fetch_status()` — line 11939 — Fetch a single Mastodon status. +- `plugin_mastodon_delete_status()` — line 11950 — Delete a Mastodon status. +- `plugin_mastodon_delete_status_should_retry_without_delete_media()` — line 11978 — Check whether a failed status deletion may be caused by Mastodon versions before 4.4.0 not understanding the optional delete_media query parameter. +- `plugin_mastodon_status_missing_response()` — line 11998 — Check whether an API response means that the referenced Mastodon status is missing. +- `plugin_mastodon_create_status()` — line 12011 — Create a Mastodon status. +- `plugin_mastodon_update_status()` — line 12038 — Update an existing Mastodon status. ## H. Import/export builders and synchronization orchestration This group composes lower-level helpers into visible synchronization behavior. It is the first group to inspect when automatic sync, normal manual sync, full manual sync, full deletion continuation or rotating known-thread refresh behaves differently after a state-format change. -- `plugin_mastodon_build_entry_status_text()` — line 12010 — Build the status body used when exporting a FlatPress entry. -- `plugin_mastodon_build_comment_status_text()` — line 12078 — Build the status body used when exporting a FlatPress comment. -- `plugin_mastodon_imported_status_footer_bbcode()` — line 12118 — Build the FlatPress BBCode footer that links an imported entry back to its source Mastodon status with a new-tab target. -- `plugin_mastodon_import_remote_entry()` — line 12134 — Import a remote Mastodon status into FlatPress as an entry. -- `plugin_mastodon_remote_status_author_label()` — line 12245 — Build a readable account label for a Mastodon status author. -- `plugin_mastodon_strip_leading_quote_block()` — line 12278 — Remove one leading BBCode quote block from imported comment text. -- `plugin_mastodon_imported_reply_quote_payload()` — line 12311 — Resolve the author and body that should be quoted for an imported Mastodon reply. -- `plugin_mastodon_build_imported_reply_quote()` — line 12354 — Build an optional BBCode quote block for an imported Mastodon reply. -- `plugin_mastodon_import_remote_comment()` — line 12396 — Import a remote Mastodon reply into FlatPress as a comment. -- `plugin_mastodon_import_remote_context_descendants()` — line 12701 — Import remote Mastodon replies from a fetched thread context. -- `plugin_mastodon_old_thread_context_rotation_limit()` — line 12811 — Return the maximum number of known synchronized threads checked for replies per content sync run. -- `plugin_mastodon_collect_known_entry_context_targets()` — line 12828 — Collect known synchronized entry threads that should have their Mastodon reply context refreshed. -- `plugin_mastodon_sync_remote_to_local()` — line 12904 — Synchronize remote Mastodon content into FlatPress. -- `plugin_mastodon_sync_local_to_remote()` — line 12991 — Synchronize local FlatPress content to Mastodon. -- `plugin_mastodon_run_deletion_sync()` — line 13254 — Run the deletion synchronization in a follow-up request after content sync completed. -- `plugin_mastodon_run_sync()` — line 13657 — Run a full synchronization cycle. +- `plugin_mastodon_build_entry_status_text()` — line 12064 — Build the status body used when exporting a FlatPress entry. +- `plugin_mastodon_build_comment_status_text()` — line 12132 — Build the status body used when exporting a FlatPress comment. +- `plugin_mastodon_imported_status_footer_bbcode()` — line 12172 — Build the FlatPress BBCode footer that links an imported entry back to its source Mastodon status with a new-tab target. +- `plugin_mastodon_import_remote_entry()` — line 12188 — Import a remote Mastodon status into FlatPress as an entry. +- `plugin_mastodon_remote_status_author_label()` — line 12299 — Build a readable account label for a Mastodon status author. +- `plugin_mastodon_strip_leading_quote_block()` — line 12332 — Remove one leading BBCode quote block from imported comment text. +- `plugin_mastodon_imported_reply_quote_payload()` — line 12365 — Resolve the author and body that should be quoted for an imported Mastodon reply. +- `plugin_mastodon_build_imported_reply_quote()` — line 12408 — Build an optional BBCode quote block for an imported Mastodon reply. +- `plugin_mastodon_import_remote_comment()` — line 12450 — Import a remote Mastodon reply into FlatPress as a comment. +- `plugin_mastodon_import_remote_context_descendants()` — line 12755 — Import remote Mastodon replies from a fetched thread context. +- `plugin_mastodon_old_thread_context_rotation_limit()` — line 12865 — Return the maximum number of known synchronized threads checked for replies per content sync run. +- `plugin_mastodon_collect_known_entry_context_targets()` — line 12882 — Collect known synchronized entry threads that should have their Mastodon reply context refreshed. +- `plugin_mastodon_sync_remote_to_local()` — line 12958 — Synchronize remote Mastodon content into FlatPress. +- `plugin_mastodon_sync_local_to_remote()` — line 13045 — Synchronize local FlatPress content to Mastodon. +- `plugin_mastodon_run_deletion_sync()` — line 13308 — Run the deletion synchronization in a follow-up request after content sync completed. +- `plugin_mastodon_run_sync()` — line 13711 — Run a full synchronization cycle. ## Recommended reading order for new developers @@ -695,62 +696,62 @@ This group composes lower-level helpers into visible synchronization behavior. I ## Alphabetical appendix / Generated function catalog The appendix is alphabetical and repeats every callable function/method with the current source line and PHPDoc-derived summary. The consistency checker verifies the function names, line numbers and that every generated entry has a non-empty description. -- `main()` — line 14171 — Keep the FlatPress admin panel lifecycle stable without additional processing. -- `onsubmit()` — line 14175 — Process submitted Mastodon admin actions for the active admin panel. +- `main()` — line 14225 — Keep the FlatPress admin panel lifecycle stable without additional processing. +- `onsubmit()` — line 14229 — Process submitted Mastodon admin actions for the active admin panel. - `plugin_mastodon_absolute_url()` — line 6749 — Convert a URL or path into an absolute URL when possible. - `plugin_mastodon_account_statuses_exclude_direct_should_retry_without()` — line 9597 — Check whether a failed account-status request may be retried without the optional `exclude_direct` filter for compatible or legacy Mastodon servers. -- `plugin_mastodon_add_dirty_entry_files()` — line 10527 — Append existing dirty entry files by canonical FlatPress ID. +- `plugin_mastodon_add_dirty_entry_files()` — line 10581 — Append existing dirty entry files by canonical FlatPress ID. - `plugin_mastodon_add_local_media_item()` — line 8574 — Append a local media item to the collection while avoiding duplicates. -- `plugin_mastodon_admin_add_info_row()` — line 13803 — Add one admin diagnostics row when the value is available. +- `plugin_mastodon_admin_add_info_row()` — line 13857 — Add one admin diagnostics row when the value is available. - `plugin_mastodon_admin_apply_save_post()` — line 2029 — Apply submitted admin settings while preserving hidden remote-import options. -- `plugin_mastodon_admin_assign()` — line 14029 — Assign plugin data to Smarty for the admin panel. -- `plugin_mastodon_admin_boolean_label()` — line 13788 — Return a localized yes/no/unknown label for admin diagnostics. -- `plugin_mastodon_admin_empty_state_maintenance_result()` — line 13942 — Return an empty admin maintenance result container. -- `plugin_mastodon_admin_instance_info_rows()` — line 13850 — Build the rows for the admin instance-information diagnostics table. +- `plugin_mastodon_admin_assign()` — line 14083 — Assign plugin data to Smarty for the admin panel. +- `plugin_mastodon_admin_boolean_label()` — line 13842 — Return a localized yes/no/unknown label for admin diagnostics. +- `plugin_mastodon_admin_empty_state_maintenance_result()` — line 13996 — Return an empty admin maintenance result container. +- `plugin_mastodon_admin_instance_info_rows()` — line 13904 — Build the rows for the admin instance-information diagnostics table. - `plugin_mastodon_admin_language_strings()` — line 6818 — Return Mastodon admin-language strings with guaranteed maintenance fallbacks. -- `plugin_mastodon_admin_panel_link()` — line 13922 — Build a local admin-panel link for Mastodon plugin pages. -- `plugin_mastodon_admin_state_maintenance_result()` — line 13958 — Convert comment-shard diagnostics into template-friendly admin rows. +- `plugin_mastodon_admin_panel_link()` — line 13976 — Build a local admin-panel link for Mastodon plugin pages. +- `plugin_mastodon_admin_state_maintenance_result()` — line 14012 — Convert comment-shard diagnostics into template-friendly admin rows. - `plugin_mastodon_apcu_cache_key()` — line 1235 — Build the plugin APCu key suffix passed to the FlatPress APCu wrappers. - `plugin_mastodon_apcu_delete()` — line 1274 — Delete a value from APCu through the FlatPress namespace helper. - `plugin_mastodon_apcu_enabled()` — line 1226 — Check whether shared APCu caching is available for the plugin. - `plugin_mastodon_apcu_fetch()` — line 1246 — Fetch a value from APCu through the FlatPress namespace helper. - `plugin_mastodon_apcu_store()` — line 1261 — Store a value in APCu through the FlatPress namespace helper. - `plugin_mastodon_apply_file_permissions()` — line 1089 — Apply FlatPress FILE_PERMISSIONS to a runtime file. -- `plugin_mastodon_array_contains_only_form_scalars()` — line 10891 — Detect whether a list contains only scalar-compatible form values. -- `plugin_mastodon_array_is_list()` — line 10871 — Detect whether a value is a numerically indexed list. +- `plugin_mastodon_array_contains_only_form_scalars()` — line 10945 — Detect whether a list contains only scalar-compatible form values. +- `plugin_mastodon_array_is_list()` — line 10925 — Detect whether a value is a numerically indexed list. - `plugin_mastodon_audiovideo_plugin_active()` — line 7020 — Determine whether the Audio/Video plugin is active for the current FlatPress request. - `plugin_mastodon_bbcode_attr_escape()` — line 8232 — Escape a value for safe BBCode attribute usage. - `plugin_mastodon_bbcode_plugin_active()` — line 6986 — Determine whether the BBCode plugin is active for the current FlatPress request. - `plugin_mastodon_bbcode_text_escape()` — line 8244 — Escape plain text embedded between BBCode tags. - `plugin_mastodon_blog_base_url()` — line 6696 — Return the absolute base URL of the current FlatPress installation. -- `plugin_mastodon_build_authorize_url()` — line 11201 — Build the OAuth authorization URL. +- `plugin_mastodon_build_authorize_url()` — line 11255 — Build the OAuth authorization URL. - `plugin_mastodon_build_comment_remote_child_index()` — line 6232 — Build an index of mapped local comments grouped by their direct remote parent status. -- `plugin_mastodon_build_comment_status_text()` — line 12078 — Build the status body used when exporting a FlatPress comment. -- `plugin_mastodon_build_entry_status_text()` — line 12010 — Build the status body used when exporting a FlatPress entry. +- `plugin_mastodon_build_comment_status_text()` — line 12132 — Build the status body used when exporting a FlatPress comment. +- `plugin_mastodon_build_entry_status_text()` — line 12064 — Build the status body used when exporting a FlatPress entry. - `plugin_mastodon_build_flatpress_tag_bbcode()` — line 7307 — Build Tag plugin BBCode from a list of remote Mastodon tags. - `plugin_mastodon_build_imported_media_bbcode()` — line 9190 — Build FlatPress BBCode for imported remote media attachments. -- `plugin_mastodon_build_imported_reply_quote()` — line 12354 — Build an optional BBCode quote block for an imported Mastodon reply. +- `plugin_mastodon_build_imported_reply_quote()` — line 12408 — Build an optional BBCode quote block for an imported Mastodon reply. - `plugin_mastodon_cleanup_imported_text()` — line 7565 — Clean imported text before saving it to FlatPress. -- `plugin_mastodon_cleanup_uploaded_media()` — line 9976 — Best-effort cleanup for uploaded Mastodon media that never reached a final status request. +- `plugin_mastodon_cleanup_uploaded_media()` — line 10030 — Best-effort cleanup for uploaded Mastodon media that never reached a final status request. - `plugin_mastodon_clear_saved_instance_info()` — line 198 — Remove any stored Mastodon instance information from the plugin options. -- `plugin_mastodon_cli_comment_shard_maintenance()` — line 13995 — Print command-line comment-shard diagnostics or perform a repair. -- `plugin_mastodon_collect_entry_files()` — line 10439 — Collect canonical entry files directly from FlatPress YY/MM content directories. -- `plugin_mastodon_collect_entry_files_for_sync()` — line 10547 — Collect local entry files for a local-to-remote synchronization pass; scheduled runs add all dirty parents without a hard cap. -- `plugin_mastodon_collect_entry_files_from_month()` — line 10378 — Collect direct entry files from one canonical FlatPress YY/MM content directory. -- `plugin_mastodon_collect_entry_files_legacy()` — line 10411 — Legacy fallback for non-canonical content trees. -- `plugin_mastodon_collect_known_entry_context_targets()` — line 12828 — Collect known synchronized entry threads that should have their Mastodon reply context refreshed. +- `plugin_mastodon_cli_comment_shard_maintenance()` — line 14049 — Print command-line comment-shard diagnostics or perform a repair. +- `plugin_mastodon_collect_entry_files()` — line 10493 — Collect canonical entry files directly from FlatPress YY/MM content directories. +- `plugin_mastodon_collect_entry_files_for_sync()` — line 10601 — Collect local entry files for a local-to-remote synchronization pass; scheduled runs add all dirty parents without a hard cap. +- `plugin_mastodon_collect_entry_files_from_month()` — line 10432 — Collect direct entry files from one canonical FlatPress YY/MM content directory. +- `plugin_mastodon_collect_entry_files_legacy()` — line 10465 — Legacy fallback for non-canonical content trees. +- `plugin_mastodon_collect_known_entry_context_targets()` — line 12882 — Collect known synchronized entry threads that should have their Mastodon reply context refreshed. - `plugin_mastodon_collect_local_entry_media()` — line 8623 — Collect local images, galleries, audio and video referenced by an entry. -- `plugin_mastodon_collect_scheduled_entry_files()` — line 10482 — Collect scheduled-sync entry candidates directly from relevant FlatPress YY/MM content directories. +- `plugin_mastodon_collect_scheduled_entry_files()` — line 10536 — Collect scheduled-sync entry candidates directly from relevant FlatPress YY/MM content directories. - `plugin_mastodon_comment_hash()` — line 8069 — Build a change-detection hash for a FlatPress comment. - `plugin_mastodon_comment_parent_fields()` — line 6504 — Return the comment fields that may contain a parent reference. - `plugin_mastodon_comment_to_reply_optin_required()` — line 2612 — Check whether comment/reply sync permits asking for Mastodon/Fediverse export approval. - `plugin_mastodon_comment_to_reply_optin_submitted()` — line 2625 — Read the optional visitor checkbox from the submitted comment form. - `plugin_mastodon_compact_instance_document()` — line 217 — Reduce a live Mastodon instance document to the stable subset that is useful for the plugin. - `plugin_mastodon_companion_plugins_status()` — line 7056 — Return mode-aware companion FlatPress plugin diagnostics; one-way mode hides import-only helpers and keeps export helpers. -- `plugin_mastodon_compare_local_entries_for_export()` — line 10595 — Compare local FlatPress entries for Mastodon export order. +- `plugin_mastodon_compare_local_entries_for_export()` — line 10649 — Compare local FlatPress entries for Mastodon export order. - `plugin_mastodon_configured_status_language()` — line 2447 — Read the configured FlatPress locale and return the Mastodon language code. -- `plugin_mastodon_content_sync_comment_entry_ids()` — line 10656 — Return entry ids whose comment shards may be needed by the current content-sync workset. -- `plugin_mastodon_create_status()` — line 11957 — Create a Mastodon status. +- `plugin_mastodon_content_sync_comment_entry_ids()` — line 10710 — Return entry ids whose comment shards may be needed by the current content-sync workset. +- `plugin_mastodon_create_status()` — line 12011 — Create a Mastodon status. - `plugin_mastodon_current_user_is_authenticated()` — line 2664 — Check whether the current FlatPress request belongs to an authenticated user without treating a later admin moderation action as comment authorship. - `plugin_mastodon_date_matches_content_window()` — line 3045 — Determine whether a date is inside the manual lower bound and, for scheduled runs, the automatic window. - `plugin_mastodon_date_matches_sync_start()` — line 3010 — Determine whether a content date passes the configured sync start date. @@ -759,12 +760,12 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_default_deletion_stats()` — line 578 — Return the default counters for the last deletion synchronization. - `plugin_mastodon_default_options()` — line 163 — Return the default plugin option values. - `plugin_mastodon_default_state()` — line 591 — Return the default runtime state structure. -- `plugin_mastodon_delete_media_attachment()` — line 9953 — Delete an uploaded Mastodon media attachment before it is attached to a final status. -- `plugin_mastodon_delete_status()` — line 11896 — Delete a Mastodon status. -- `plugin_mastodon_delete_status_should_retry_without_delete_media()` — line 11924 — Check whether a failed status deletion may be caused by Mastodon versions before 4.4.0 not understanding the optional delete_media query parameter. +- `plugin_mastodon_delete_media_attachment()` — line 10007 — Delete an uploaded Mastodon media attachment before it is attached to a final status. +- `plugin_mastodon_delete_status()` — line 11950 — Delete a Mastodon status. +- `plugin_mastodon_delete_status_should_retry_without_delete_media()` — line 11978 — Check whether a failed status deletion may be caused by Mastodon versions before 4.4.0 not understanding the optional delete_media query parameter. - `plugin_mastodon_deletion_sync_due()` — line 6195 — Determine whether a pending deletion synchronization may start now using UTC cooldown timestamps. - `plugin_mastodon_detect_local_comment_parent_id()` — line 6530 — Detect the local parent comment identifier from comment data. -- `plugin_mastodon_dirty_entry_id_lookup()` — line 10626 — Return entry identifiers that are explicitly queued by dirty entry/comment hooks. +- `plugin_mastodon_dirty_entry_id_lookup()` — line 10680 — Return entry identifiers that are explicitly queued by dirty entry/comment hooks. - `plugin_mastodon_dirty_tracking_options()` — line 5598 — Check whether dirty tracking should persist state for a local FlatPress write hook. - `plugin_mastodon_dom_children_to_flatpress()` — line 7619 — Convert DOM child nodes into FlatPress BBCode text. - `plugin_mastodon_dom_node_to_flatpress()` — line 7637 — Convert a single DOM node into FlatPress BBCode text. @@ -774,25 +775,25 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_emoticons_plugin_active()` — line 7037 — Determine whether the Emoticons plugin is active for the current FlatPress request. - `plugin_mastodon_enabled_plugin_state()` — line 6904 — Determine whether a FlatPress plugin is enabled in the centralized plugin configuration. - `plugin_mastodon_ensure_comment_shard_dir()` — line 3247 — Ensure that the comment-shard directory exists. -- `plugin_mastodon_ensure_profile_dir()` — line 11270 — Ensure the public profile-widget cache directory and directory-listing guard exist. +- `plugin_mastodon_ensure_profile_dir()` — line 11324 — Ensure the public profile-widget cache directory and directory-listing guard exist. - `plugin_mastodon_ensure_state_dir()` — line 3239 — Ensure that the plugin runtime directory exists. - `plugin_mastodon_entry_hash()` — line 8057 — Build a change-detection hash for a FlatPress entry. - `plugin_mastodon_entry_media_attachment_signature_from_items()` — line 8858 — Build a signature for the actual media payload of local entry attachments. - `plugin_mastodon_entry_media_description_signature_from_items()` — line 8881 — Build a signature for local entry media descriptions. - `plugin_mastodon_entry_media_signature()` — line 8900 — Build a signature for media references contained in entry content. -- `plugin_mastodon_entry_month_end_date_key()` — line 10336 — Return the last date key that can occur inside a FlatPress YY/MM content directory. -- `plugin_mastodon_entry_month_may_match_scheduled_window()` — line 10358 — Determine whether a FlatPress YY/MM directory can contain scheduled-sync entry candidates. -- `plugin_mastodon_exchange_code_for_token()` — line 11221 — Exchange an OAuth authorization code for an access token. +- `plugin_mastodon_entry_month_end_date_key()` — line 10390 — Return the last date key that can occur inside a FlatPress YY/MM content directory. +- `plugin_mastodon_entry_month_may_match_scheduled_window()` — line 10412 — Determine whether a FlatPress YY/MM directory can contain scheduled-sync entry candidates. +- `plugin_mastodon_exchange_code_for_token()` — line 11275 — Exchange an OAuth authorization code for an access token. - `plugin_mastodon_extend_time_limit()` — line 9386 — Best-effort refresh/increase of the PHP execution time budget for long-running Mastodon work. - `plugin_mastodon_extension_from_mime_type()` — line 8367 — Return an appropriate file extension for a MIME type. - `plugin_mastodon_extract_flatpress_tags()` — line 7154 — Extract FlatPress Tag plugin labels from an entry body. - `plugin_mastodon_extract_url_token()` — line 6732 — Extract the URL token from a BBCode or attribute fragment. - `plugin_mastodon_fediverse_creator_value()` — line 2356 — Build the fediverse creator meta value. -- `plugin_mastodon_fetch_account_statuses()` — line 11793 — Fetch statuses for the authenticated Mastodon account. -- `plugin_mastodon_fetch_media_attachment()` — line 9943 — Fetch the current processing status of a Mastodon media attachment. -- `plugin_mastodon_fetch_reply_notifications()` — line 11862 — Fetch recent mention notifications for prioritized old-thread reply hints. -- `plugin_mastodon_fetch_status()` — line 11885 — Fetch a single Mastodon status. -- `plugin_mastodon_fetch_status_context()` — line 11851 — Fetch the conversation context for a Mastodon status. +- `plugin_mastodon_fetch_account_statuses()` — line 11847 — Fetch statuses for the authenticated Mastodon account. +- `plugin_mastodon_fetch_media_attachment()` — line 9997 — Fetch the current processing status of a Mastodon media attachment. +- `plugin_mastodon_fetch_reply_notifications()` — line 11916 — Fetch recent mention notifications for prioritized old-thread reply hints. +- `plugin_mastodon_fetch_status()` — line 11939 — Fetch a single Mastodon status. +- `plugin_mastodon_fetch_status_context()` — line 11905 — Fetch the conversation context for a Mastodon status. - `plugin_mastodon_file_permissions_mode()` — line 1080 — Return the FlatPress file permission mode for runtime files. - `plugin_mastodon_file_prestat()` — line 1927 — Read a cheap file metadata snapshot for cache validation. - `plugin_mastodon_file_prestat_signature()` — line 1947 — Convert a file metadata snapshot into a stable cache signature. @@ -806,25 +807,25 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_guess_subject()` — line 6645 — Guess a subject line from imported plain text. - `plugin_mastodon_head()` — line 2374 — Print Mastodon profile metadata into the HTML head. - `plugin_mastodon_html_entity_decode()` — line 6687 — Decode HTML entities using the plugin defaults. -- `plugin_mastodon_http_build_query()` — line 10909 — Build an application/x-www-form-urlencoded query string for Mastodon requests. -- `plugin_mastodon_http_request()` — line 10965 — Perform an HTTP request using cURL or the stream fallback. -- `plugin_mastodon_http_request_multipart()` — line 9810 — Perform a multipart HTTP request. -- `plugin_mastodon_import_remote_comment()` — line 12396 — Import a remote Mastodon reply into FlatPress as a comment. -- `plugin_mastodon_import_remote_context_descendants()` — line 12701 — Import remote Mastodon replies from a fetched thread context. -- `plugin_mastodon_import_remote_entry()` — line 12134 — Import a remote Mastodon status into FlatPress as an entry. -- `plugin_mastodon_import_remote_notification_context()` — line 12569 — Use a notification status context to import an unmapped parent chain below a known entry. -- `plugin_mastodon_import_remote_notification_reply()` — line 12528 — Directly import a notification reply when its mapped parent entry or comment is already known. -- `plugin_mastodon_imported_reply_quote_payload()` — line 12311 — Resolve the author and body that should be quoted for an imported Mastodon reply. -- `plugin_mastodon_imported_status_footer_bbcode()` — line 12118 — Build the FlatPress BBCode footer that links an imported entry back to its source Mastodon status with a new-tab target. +- `plugin_mastodon_http_build_query()` — line 10963 — Build an application/x-www-form-urlencoded query string for Mastodon requests. +- `plugin_mastodon_http_request()` — line 11019 — Perform an HTTP request using cURL or the stream fallback. +- `plugin_mastodon_http_request_multipart()` — line 9864 — Perform a multipart HTTP request. +- `plugin_mastodon_import_remote_comment()` — line 12450 — Import a remote Mastodon reply into FlatPress as a comment. +- `plugin_mastodon_import_remote_context_descendants()` — line 12755 — Import remote Mastodon replies from a fetched thread context. +- `plugin_mastodon_import_remote_entry()` — line 12188 — Import a remote Mastodon status into FlatPress as an entry. +- `plugin_mastodon_import_remote_notification_context()` — line 12623 — Use a notification status context to import an unmapped parent chain below a known entry. +- `plugin_mastodon_import_remote_notification_reply()` — line 12582 — Directly import a notification reply when its mapped parent entry or comment is already known. +- `plugin_mastodon_imported_reply_quote_payload()` — line 12365 — Resolve the author and body that should be quoted for an imported Mastodon reply. +- `plugin_mastodon_imported_status_footer_bbcode()` — line 12172 — Build the FlatPress BBCode footer that links an imported entry back to its source Mastodon status with a new-tab target. - `plugin_mastodon_instance_authority()` — line 2310 — Return the Mastodon instance authority used in fediverse creator metadata. -- `plugin_mastodon_instance_character_limit()` — line 11778 — Return the status character limit of the configured instance. +- `plugin_mastodon_instance_character_limit()` — line 11832 — Return the status character limit of the configured instance. - `plugin_mastodon_instance_configuration()` — line 9649 — Load and cache the Mastodon instance configuration document. - `plugin_mastodon_instance_document()` — line 9417 — Load and cache the full Mastodon instance document returned by /api/v2/instance. - `plugin_mastodon_instance_document_api_version()` — line 9492 — Return the machine-readable Mastodon API compatibility version from an instance document. - `plugin_mastodon_instance_media_description_limit()` — line 9672 — Return the media description length limit of the configured instance. - `plugin_mastodon_instance_media_limit()` — line 9659 — Return the media attachment limit of the configured instance. - `plugin_mastodon_instance_media_size_limit()` — line 8434 — Return the configured byte-size limit for a media family, or 0 if unknown. -- `plugin_mastodon_instance_registration_summary()` — line 13824 — Summarize the registration state advertised by the Mastodon instance. +- `plugin_mastodon_instance_registration_summary()` — line 13878 — Summarize the registration state advertised by the Mastodon instance. - `plugin_mastodon_instance_supported_media_mime_types()` — line 8414 — Return instance-advertised supported media MIME types. - `plugin_mastodon_instance_supports_account_statuses_exclude_direct()` — line 9574 — Determine whether cached instance information confirms support for the Mastodon 4.6 `exclude_direct` account-status filter. - `plugin_mastodon_instance_supports_mastodon_api_v4()` — line 9544 — Centralizes cached `api_versions[mastodon] >= 4` / Mastodon `>= 4.4.0` capability decisions for status deletion and unattached-media cleanup without extra network requests. @@ -838,16 +839,16 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_io_rotate_file_if_needed()` — line 1187 — Rotate an append-only log file when the next append would exceed the size cap. - `plugin_mastodon_io_write_file()` — line 1102 — Write a file through the FlatPress I/O layer when available. - `plugin_mastodon_is_public_host()` — line 7437 — Determine whether a host name resolves to a public endpoint. -- `plugin_mastodon_is_two_digit_path_segment()` — line 10316 — Determine whether a FlatPress content-tree path segment is a two-digit date segment. +- `plugin_mastodon_is_two_digit_path_segment()` — line 10370 — Determine whether a FlatPress content-tree path segment is a two-digit date segment. - `plugin_mastodon_json_decode_string_at()` — line 3380 — Decode a JSON string literal starting at the current offset. - `plugin_mastodon_json_skip_ws()` — line 3417 — Skip JSON whitespace in a string scanner. - `plugin_mastodon_json_top_level_property_bounds()` — line 3434 — Find a top-level JSON property value in an object payload without decoding the whole object. - `plugin_mastodon_lang_string()` — line 6791 — Return a localized plugin string or a provided fallback. -- `plugin_mastodon_limit_status_text()` — line 9731 — Limit Mastodon plain text while respecting the instance URL budget. +- `plugin_mastodon_limit_status_text()` — line 9784 — Limit Mastodon plain text while respecting the instance URL budget. - `plugin_mastodon_limit_text()` — line 8035 — Limit text to a maximum number of characters. - `plugin_mastodon_list_local_comment_ids()` — line 6608 — List local FlatPress comment identifiers by scanning the entry comment directory directly. -- `plugin_mastodon_list_local_entries()` — line 10763 — List local FlatPress entries ordered for export. -- `plugin_mastodon_list_local_entries_for_sync()` — line 10724 — List local FlatPress entries for a local-to-remote synchronization pass. +- `plugin_mastodon_list_local_entries()` — line 10817 — List local FlatPress entries ordered for export. +- `plugin_mastodon_list_local_entries_for_sync()` — line 10778 — List local FlatPress entries for a local-to-remote synchronization pass. - `plugin_mastodon_local_comment_export_allowed()` — line 5019 — Allow new local comment exports only with a stored visitor/authenticated grant while preserving existing mappings. - `plugin_mastodon_local_comment_export_grant_source()` — line 2681 — Determine whether a newly saved local comment receives a frontend or authenticated Mastodon export grant. - `plugin_mastodon_local_comment_from_authenticated_author()` — line 2797 — Detect authenticated FlatPress/admin comments that do not require the public visitor opt-in marker. @@ -856,7 +857,7 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_local_item_date_key()` — line 2937 — Determine the date key of a local FlatPress entry or comment. - `plugin_mastodon_local_item_matches_content_window()` — line 3079 — Determine whether a local FlatPress item is inside the active content synchronization window. - `plugin_mastodon_local_item_matches_sync_start()` — line 3067 — Determine whether a local FlatPress item should be synchronized. -- `plugin_mastodon_local_item_timestamp()` — line 10566 — Resolve the best timestamp for a local FlatPress item. +- `plugin_mastodon_local_item_timestamp()` — line 10620 — Resolve the best timestamp for a local FlatPress item. - `plugin_mastodon_local_write_guard_active()` — line 5590 — Return whether FlatPress write hooks are currently triggered by Mastodon remote mirroring. - `plugin_mastodon_local_write_guard_enter()` — line 5567 — Increase the local-write guard depth while the plugin mirrors remote Mastodon data into FlatPress. - `plugin_mastodon_local_write_guard_leave()` — line 5578 — Decrease the local-write guard depth after a plugin-owned FlatPress write. @@ -869,11 +870,11 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_mapping_keys_after_cursor()` — line 3221 — Return mapping keys ordered after a saved deletion cursor. - `plugin_mastodon_mapping_matches_deletion_lookup_window()` — line 3200 — Determine whether an existing local mapping should be remotely checked for deletion in this run. - `plugin_mastodon_mapping_matches_sync_start()` — line 3111 — Determine whether a synchronized mapping should participate in the deletion follow-up for the current sync start date. -- `plugin_mastodon_mastodon_api()` — line 11084 — Call the Mastodon API and return the raw HTTP response. +- `plugin_mastodon_mastodon_api()` — line 11138 — Call the Mastodon API and return the raw HTTP response. - `plugin_mastodon_mastodon_hashtag_footer()` — line 7196 — Convert FlatPress tag labels into a Mastodon hashtag footer line. - `plugin_mastodon_mastodon_html_to_flatpress()` — line 7816 — Convert Mastodon HTML content into FlatPress BBCode. -- `plugin_mastodon_mastodon_json()` — line 11134 — Call the Mastodon API and decode a JSON response. -- `plugin_mastodon_maybe_sync()` — line 13749 — Run the scheduled synchronization when the current request is due. +- `plugin_mastodon_mastodon_json()` — line 11188 — Call the Mastodon API and decode a JSON response. +- `plugin_mastodon_maybe_sync()` — line 13803 — Run the scheduled synchronization when the current request is due. - `plugin_mastodon_media_copy_tree()` — line 8194 — Copy a directory tree used for media synchronization. - `plugin_mastodon_media_delete_tree()` — line 8167 — Delete a directory tree used for imported media. - `plugin_mastodon_media_description_from_bbcode_content()` — line 8519 — Normalize optional AudioVideo BBCode content into a Mastodon media description. @@ -882,9 +883,9 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_media_guess_mime_type()` — line 8266 — Guess the MIME type of a local media file. - `plugin_mastodon_media_parse_tag_attributes()` — line 8488 — Parse key/value attributes from a FlatPress media tag. - `plugin_mastodon_media_prepare_directory()` — line 8151 — Ensure that a media directory exists. -- `plugin_mastodon_media_processing_attempts()` — line 10018 — Determine how patiently the plugin should wait for Mastodon media processing. +- `plugin_mastodon_media_processing_attempts()` — line 10072 — Determine how patiently the plugin should wait for Mastodon media processing. - `plugin_mastodon_media_relative_to_absolute()` — line 8138 — Resolve a FlatPress media path to an absolute file path. -- `plugin_mastodon_media_transfer_timeout()` — line 10042 — Determine a practical transfer timeout for one media upload. +- `plugin_mastodon_media_transfer_timeout()` — line 10096 — Determine a practical transfer timeout for one media upload. - `plugin_mastodon_media_type_from_mime()` — line 8333 — Return a stable FlatPress/Mastodon media family for a MIME type. - `plugin_mastodon_minutes_to_sync_time()` — line 971 — Convert minutes after midnight into a normalized HH:MM time value. - `plugin_mastodon_normalize_boolean_option()` — line 2535 — Normalize a boolean-like option value to the stored string representation. @@ -919,7 +920,7 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_oauth_server_metadata()` — line 654 — Fetch OAuth authorization server metadata for the configured Mastodon instance. - `plugin_mastodon_oauth_supported_scopes()` — line 673 — Return the OAuth scopes supported by the configured Mastodon instance, if discoverable. - `plugin_mastodon_old_thread_context_limit_choices()` — line 2883 — Build the 1-10 admin select choices for old-thread context refreshes. -- `plugin_mastodon_old_thread_context_rotation_limit()` — line 12811 — Return the maximum number of known synchronized threads checked for replies per content sync run. +- `plugin_mastodon_old_thread_context_rotation_limit()` — line 12865 — Return the maximum number of known synchronized threads checked for replies per content sync run. - `plugin_mastodon_on_comment_deleted()` — line 5778 — Immediately protect mapped deletions: preserve and tombstone `source=local` for remote deletion, or ignore/unmap `source=remote` without remote deletion. - `plugin_mastodon_on_comment_saved()` — line 5693 — Mark a local FlatPress comment write as dirty for a later Mastodon sync. - `plugin_mastodon_on_commentcenter_comment_discarded()` — line 2766 — Remove or finalize a pending opt-in grant when CommentCenter permanently discards its pending comment. @@ -927,27 +928,27 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_on_entry_deleted()` — line 5666 — Mark a local FlatPress entry deletion for the Mastodon deletion sync. - `plugin_mastodon_on_entry_saved()` — line 5617 — Mark a local FlatPress entry write as dirty for a later Mastodon sync. - `plugin_mastodon_optin_comment_to_reply()` — line 2702 — Render the optional public comment-form Mastodon/Fediverse opt-in checkbox. -- `plugin_mastodon_parse_http_response_headers()` — line 10801 — Parse raw HTTP response headers. +- `plugin_mastodon_parse_http_response_headers()` — line 10855 — Parse raw HTTP response headers. - `plugin_mastodon_parse_iso_datetime()` — line 6418 — Parse an ISO date/time string into FlatPress date format. - `plugin_mastodon_parse_iso_timestamp()` — line 6437 — Parse an ISO date/time value into a Unix timestamp. - `plugin_mastodon_photoswipe_plugin_active()` — line 7003 — Determine whether the PhotoSwipe plugin is active for the current FlatPress request. - `plugin_mastodon_plain_text_from_bbcode()` — line 7479 — Convert FlatPress BBCode into plain text for Mastodon export. - `plugin_mastodon_prepare_emoticons_for_mastodon()` — line 7402 — Convert FlatPress emoticon shortcodes to Mastodon-safe Unicode glyphs when the plugin is active. - `plugin_mastodon_prepare_entry_media_items()` — line 8830 — Normalize local entry media items for signature and export planning. -- `plugin_mastodon_prepare_entry_media_sync_plan()` — line 10228 — Decide whether a local entry update can reuse already-uploaded Mastodon media or needs a fresh upload. +- `plugin_mastodon_prepare_entry_media_sync_plan()` — line 10282 — Decide whether a local entry update can reuse already-uploaded Mastodon media or needs a fresh upload. - `plugin_mastodon_process_pending_comment_remote_rechecks()` — line 6302 — Process queued descendant reply rechecks using a small FIFO queue. -- `plugin_mastodon_process_remote_reply_notifications()` — line 12625 — Process mention notifications as budgeted reply hints before normal old-thread rotation. -- `plugin_mastodon_profile_avatar_type()` — line 11467 — Detect and allow only supported avatar image MIME types. -- `plugin_mastodon_profile_avatar_url_from_account()` — line 11451 — Choose the preferred Mastodon avatar URL, preferring `avatar_static`. -- `plugin_mastodon_profile_cache_read()` — line 11379 — Read and validate the local public profile-widget cache. -- `plugin_mastodon_profile_cache_write()` — line 11420 — Write the public profile-widget cache without storing secrets. -- `plugin_mastodon_profile_download_avatar()` — line 11518 — Download, validate and atomically store the avatar in the local profile cache directory. -- `plugin_mastodon_profile_public_url_allowed()` — line 11327 — Validate public HTTP(S) profile and avatar URLs before storing them. -- `plugin_mastodon_profile_relative_to_absolute()` — line 11345 — Resolve a profile-cache path below `fp-content/plugin_mastodon/profile/` to a local filesystem path. -- `plugin_mastodon_profile_relative_to_url()` — line 11362 — Resolve a profile-cache path below `fp-content/plugin_mastodon/profile/` to a public FlatPress URL. -- `plugin_mastodon_profile_text()` — line 11312 — Normalize public account text before writing it to the profile-widget cache. +- `plugin_mastodon_process_remote_reply_notifications()` — line 12679 — Process mention notifications as budgeted reply hints before normal old-thread rotation. +- `plugin_mastodon_profile_avatar_type()` — line 11521 — Detect and allow only supported avatar image MIME types. +- `plugin_mastodon_profile_avatar_url_from_account()` — line 11505 — Choose the preferred Mastodon avatar URL, preferring `avatar_static`. +- `plugin_mastodon_profile_cache_read()` — line 11433 — Read and validate the local public profile-widget cache. +- `plugin_mastodon_profile_cache_write()` — line 11474 — Write the public profile-widget cache without storing secrets. +- `plugin_mastodon_profile_download_avatar()` — line 11572 — Download, validate and atomically store the avatar in the local profile cache directory. +- `plugin_mastodon_profile_public_url_allowed()` — line 11381 — Validate public HTTP(S) profile and avatar URLs before storing them. +- `plugin_mastodon_profile_relative_to_absolute()` — line 11399 — Resolve a profile-cache path below `fp-content/plugin_mastodon/profile/` to a local filesystem path. +- `plugin_mastodon_profile_relative_to_url()` — line 11416 — Resolve a profile-cache path below `fp-content/plugin_mastodon/profile/` to a public FlatPress URL. +- `plugin_mastodon_profile_text()` — line 11366 — Normalize public account text before writing it to the profile-widget cache. - `plugin_mastodon_profile_url()` — line 2340 — Build the public Mastodon profile URL used for the rel-me link. -- `plugin_mastodon_profile_url_from_account()` — line 11438 — Choose the Mastodon profile URL from account data or the configured instance/username. +- `plugin_mastodon_profile_url_from_account()` — line 11492 — Choose the Mastodon profile URL from account data or the configured instance/username. - `plugin_mastodon_protect_locally_deleted_exported_comments()` — line 6013 — Protect locally deleted exported FlatPress comments already present in the loaded workset before deletion sync runs. - `plugin_mastodon_protect_missing_local_exported_comment_by_remote_id()` — line 5975 — Defensively tombstone a missing exported comment at the import boundary using only its reverse-indexed shard. - `plugin_mastodon_public_comment_url()` — line 7801 — Return the public URL for a specific FlatPress comment. @@ -977,10 +978,10 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_rate_limit_window_write()` — line 1589 — Persist the current rate-limit windows. - `plugin_mastodon_reattach_local_comment_to_entry_status()` — line 6063 — Reattach one imported local comment to the synchronized entry status after its remote parent reply disappeared. - `plugin_mastodon_refresh_instance_information()` — line 541 — Force a live refresh of the Mastodon instance information and persist the compact snapshot. -- `plugin_mastodon_refresh_profile_cache()` — line 11667 — Refresh the public profile-widget cache through `verify_credentials`. -- `plugin_mastodon_refresh_profile_cache_for_sync()` — line 11686 — Refresh the public profile-widget cache during content synchronization without deciding the sync outcome by itself. -- `plugin_mastodon_refresh_profile_cache_from_account()` — line 11579 — Build the public profile-widget cache from a verified Mastodon account payload. -- `plugin_mastodon_register_app()` — line 11178 — Register the FlatPress application on the configured Mastodon instance. +- `plugin_mastodon_refresh_profile_cache()` — line 11721 — Refresh the public profile-widget cache through `verify_credentials`. +- `plugin_mastodon_refresh_profile_cache_for_sync()` — line 11740 — Refresh the public profile-widget cache during content synchronization without deciding the sync outcome by itself. +- `plugin_mastodon_refresh_profile_cache_from_account()` — line 11633 — Build the public profile-widget cache from a verified Mastodon account payload. +- `plugin_mastodon_register_app()` — line 11232 — Register the FlatPress application on the configured Mastodon instance. - `plugin_mastodon_remote_download_basename()` — line 9157 — Build a safe basename for a downloaded remote attachment. - `plugin_mastodon_remote_media_attachment_type()` — line 8917 — Return the normalized Mastodon attachment type. - `plugin_mastodon_remote_media_description()` — line 9014 — Resolve the best description for a remote attachment. @@ -989,7 +990,7 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_remote_media_focus()` — line 9028 — Resolve the stored focus string for a remote attachment, if any. - `plugin_mastodon_remote_media_source_url()` — line 8974 — Resolve the best downloadable source URL for a remote attachment. - `plugin_mastodon_remote_media_source_urls()` — line 8992 — Return direct-download candidate URLs for a remote media attachment. -- `plugin_mastodon_remote_status_author_label()` — line 12245 — Build a readable account label for a Mastodon status author. +- `plugin_mastodon_remote_status_author_label()` — line 12299 — Build a readable account label for a Mastodon status author. - `plugin_mastodon_remote_status_date_key()` — line 2960 — Determine the date key of a remote Mastodon status. - `plugin_mastodon_remote_status_is_importable()` — line 6492 — Determine whether a remote Mastodon status may be imported. - `plugin_mastodon_remote_status_matches_content_window()` — line 3100 — Determine whether a remote Mastodon status is inside the active content synchronization window. @@ -1001,9 +1002,9 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_replace_emoticon_shortcodes_with_unicode()` — line 7387 — Replace FlatPress emoticon shortcodes with Unicode glyphs. - `plugin_mastodon_replace_unicode_emoticons_with_shortcodes()` — line 7415 — Replace Unicode emoticons with FlatPress shortcodes. - `plugin_mastodon_resolve_comment_reply_target()` — line 6552 — Resolve the remote reply target for a local comment export. -- `plugin_mastodon_response_error_message()` — line 11149 — Extract the most useful error message from an API response. -- `plugin_mastodon_run_deletion_sync()` — line 13254 — Run the deletion synchronization in a follow-up request after content sync completed. -- `plugin_mastodon_run_sync()` — line 13657 — Run a full synchronization cycle. +- `plugin_mastodon_response_error_message()` — line 11203 — Extract the most useful error message from an API response. +- `plugin_mastodon_run_deletion_sync()` — line 13308 — Run the deletion synchronization in a follow-up request after content sync completed. +- `plugin_mastodon_run_sync()` — line 13711 — Run a full synchronization cycle. - `plugin_mastodon_runtime_cache_clear()` — line 851 — Clear one request-local plugin cache bucket or the complete cache. - `plugin_mastodon_runtime_cache_get()` — line 809 — Return a value from the request-local plugin cache. - `plugin_mastodon_runtime_cache_set()` — line 833 — Store a value in the request-local plugin cache. @@ -1027,7 +1028,7 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_should_check_old_thread_replies()` — line 2859 — Check whether known synchronized Mastodon threads should be checked for replies in rotating batches. - `plugin_mastodon_should_import_remote_to_local()` — line 2578 — Check whether Mastodon responses may create, update or delete local FlatPress content. - `plugin_mastodon_should_import_synced_comments_as_entries()` — line 2823 — Check whether a remote Mastodon status that is already mapped to a local FlatPress comment may also be imported as an entry. -- `plugin_mastodon_should_parse_local_entry_for_sync()` — line 10699 — Determine whether a local entry file should be parsed during a scheduled local-to-remote pass. +- `plugin_mastodon_should_parse_local_entry_for_sync()` — line 10753 — Determine whether a local entry file should be parsed during a scheduled local-to-remote pass. - `plugin_mastodon_should_poll_reply_notifications()` — line 2896 — Check whether reply-notification polling is enabled and authorized. - `plugin_mastodon_should_quote_imported_reply_parent()` — line 2841 — Check whether imported Mastodon replies should include a quote of the replied-to comment. - `plugin_mastodon_should_run_deletion_sync()` — line 2914 — Check whether the follow-up deletion synchronization is enabled. @@ -1068,7 +1069,7 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_state_is_entry_id()` — line 3256 — Return whether a string looks like a FlatPress entry id. - `plugin_mastodon_state_load_comment_shard_into()` — line 4035 — Load one entry comment shard into a partial state when needed. - `plugin_mastodon_state_load_comment_shards()` — line 3933 — Load comment shards into a state array. -- `plugin_mastodon_state_load_content_sync_comment_workset()` — line 10683 — Load comment shards needed for the current content synchronization workset. +- `plugin_mastodon_state_load_content_sync_comment_workset()` — line 10737 — Load comment shards needed for the current content synchronization workset. - `plugin_mastodon_state_loaded_comment_entry_ids()` — line 3982 — Return the entry ids whose comment shards are loaded in a partial state. - `plugin_mastodon_state_main_payload()` — line 3914 — Build the main state payload without high-volume inline comment mappings. - `plugin_mastodon_state_mark_comment_entry_loaded()` — line 4015 — Mark one entry's comment shard as loaded in a partial state. @@ -1112,16 +1113,17 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_state_write_lock_release()` — line 3367 — Release a state-write lock handle. - `plugin_mastodon_state_write_with_last_error()` — line 5166 — Persist state and copy a failed write reason into the caller-visible last_error field. - `plugin_mastodon_status_media_attributes()` — line 9110 — Build media_attributes descriptors for PUT /api/v1/statuses/:id. -- `plugin_mastodon_status_missing_response()` — line 11944 — Check whether an API response means that the referenced Mastodon status is missing. -- `plugin_mastodon_status_text_length()` — line 9699 — Calculate the Mastodon-visible length of a plain-text status. +- `plugin_mastodon_status_missing_response()` — line 11998 — Check whether an API response means that the referenced Mastodon status is missing. +- `plugin_mastodon_status_text_length()` — line 9751 — Calculate the Mastodon-visible length of a plain-text status. +- `plugin_mastodon_status_text_url_spans()` — line 9703 — Extract Mastodon-countable URL spans and leave trailing sentence punctuation outside the URL placeholder budget. - `plugin_mastodon_store_instance_document()` — line 479 — Persist a compact Mastodon instance snapshot inside the plugin configuration. - `plugin_mastodon_store_instance_error()` — line 525 — Persist the latest instance-information refresh error for the admin diagnostics view. -- `plugin_mastodon_stream_context_request()` — line 10831 — Perform an HTTP request through a stream context fallback. +- `plugin_mastodon_stream_context_request()` — line 10885 — Perform an HTTP request through a stream context fallback. - `plugin_mastodon_strip_flatpress_tag_bbcode()` — line 7179 — Remove Tag plugin BBCode blocks from entry content. -- `plugin_mastodon_strip_leading_quote_block()` — line 12278 — Remove one leading BBCode quote block from imported comment text. +- `plugin_mastodon_strip_leading_quote_block()` — line 12332 — Remove one leading BBCode quote block from imported comment text. - `plugin_mastodon_strip_trailing_mastodon_hashtag_footer()` — line 7244 — Remove a trailing Mastodon hashtag footer from imported plain text. - `plugin_mastodon_subject_line_is_noise()` — line 7523 — Determine whether an extracted line should be ignored as a subject. -- `plugin_mastodon_sync_due()` — line 13626 — Determine whether the scheduled synchronization is currently due using stored UTC sync time and UTC `last_run` days. +- `plugin_mastodon_sync_due()` — line 13680 — Determine whether the scheduled synchronization is currently due using stored UTC sync time and UTC `last_run` days. - `plugin_mastodon_sync_guard_active()` — line 1405 — Return true when a recent scheduled sync/deletion pass should cool down. - `plugin_mastodon_sync_guard_apcu_key()` — line 1323 — Return the plugin-local APCu key suffix used for a sync cooldown guard. - `plugin_mastodon_sync_guard_apcu_store()` — line 1344 — Store one cooldown guard through the FlatPress APCu wrapper. @@ -1131,22 +1133,22 @@ The appendix is alphabetical and repeats every callable function/method with the - `plugin_mastodon_sync_guard_file_write()` — line 1384 — Write the file-backed cooldown guard. - `plugin_mastodon_sync_guard_kind()` — line 1313 — Normalize the cooldown guard kind. - `plugin_mastodon_sync_guard_mark()` — line 1430 — Mark a short cooldown guard for a scheduled sync/deletion pass. -- `plugin_mastodon_sync_local_to_remote()` — line 12991 — Synchronize local FlatPress content to Mastodon. -- `plugin_mastodon_sync_remote_to_local()` — line 12904 — Synchronize remote Mastodon content into FlatPress. +- `plugin_mastodon_sync_local_to_remote()` — line 13045 — Synchronize local FlatPress content to Mastodon. +- `plugin_mastodon_sync_remote_to_local()` — line 12958 — Synchronize remote Mastodon content into FlatPress. - `plugin_mastodon_sync_time_local_to_utc()` — line 998 — Convert a FlatPress-local admin synchronization time back to stored UTC time. - `plugin_mastodon_sync_time_to_minutes()` — line 960 — Convert a HH:MM time value into minutes after midnight. - `plugin_mastodon_sync_time_utc_to_local()` — line 987 — Convert the stored UTC synchronization time to the FlatPress-local admin time. - `plugin_mastodon_tag_plugin_active()` — line 6969 — Determine whether the Tag plugin is active for the current FlatPress request. -- `plugin_mastodon_test_note_local_entry_parse()` — line 10614 — Increment a simulation-only local-entry parse counter when enabled. +- `plugin_mastodon_test_note_local_entry_parse()` — line 10668 — Increment a simulation-only local-entry parse counter when enabled. - `plugin_mastodon_timestamp_date_key()` — line 2923 — Convert a FlatPress-adjusted timestamp into a stable date key. - `plugin_mastodon_timestamp_to_flatpress_time()` — line 944 — Convert a real Unix timestamp into FlatPress's offset-adjusted timestamp model. -- `plugin_mastodon_two_digit_year_to_full_year()` — line 10325 — Convert a FlatPress two-digit year segment into the full year used by PHP's legacy date parser. -- `plugin_mastodon_update_status()` — line 11984 — Update an existing Mastodon status. -- `plugin_mastodon_upload_media_items()` — line 10111 — Upload local media items to Mastodon and collect the created media IDs. +- `plugin_mastodon_two_digit_year_to_full_year()` — line 10379 — Convert a FlatPress two-digit year segment into the full year used by PHP's legacy date parser. +- `plugin_mastodon_update_status()` — line 12038 — Update an existing Mastodon status. +- `plugin_mastodon_upload_media_items()` — line 10165 — Upload local media items to Mastodon and collect the created media IDs. - `plugin_mastodon_validate_local_media_item()` — line 8461 — Validate a local media item against known instance upload limits. -- `plugin_mastodon_verify_credentials()` — line 11251 — Verify the currently configured access token. -- `plugin_mastodon_wait_for_media_attachment()` — line 10062 — Wait briefly until an asynchronously uploaded Mastodon media attachment is ready. -- `plugin_mastodon_widget()` — line 11733 — Render the compact Mastodon profile widget from local cache only. -- `plugin_mastodon_widget_avatar_alt()` — line 11712 — Build the localized avatar alt text for the public Mastodon widget. -- `plugin_mastodon_widget_lang_string()` — line 11290 — Return localized public Mastodon widget strings with safe fallbacks. -- `setup()` — line 14166 — Register the active Mastodon admin template and assign plugin data to Smarty. +- `plugin_mastodon_verify_credentials()` — line 11305 — Verify the currently configured access token. +- `plugin_mastodon_wait_for_media_attachment()` — line 10116 — Wait briefly until an asynchronously uploaded Mastodon media attachment is ready. +- `plugin_mastodon_widget()` — line 11787 — Render the compact Mastodon profile widget from local cache only. +- `plugin_mastodon_widget_avatar_alt()` — line 11766 — Build the localized avatar alt text for the public Mastodon widget. +- `plugin_mastodon_widget_lang_string()` — line 11344 — Return localized public Mastodon widget strings with safe fallbacks. +- `setup()` — line 14220 — Register the active Mastodon admin template and assign plugin data to Smarty. diff --git a/fp-plugins/mastodon/developer-docs/README.md b/fp-plugins/mastodon/developer-docs/README.md index ada4db2f..23ebe88a 100644 --- a/fp-plugins/mastodon/developer-docs/README.md +++ b/fp-plugins/mastodon/developer-docs/README.md @@ -19,19 +19,20 @@ It is written for developers who need to make safe changes without breaking sync Use these starting points when the requested change is framed by behavior rather than by function name: -| Requested change | Start reading | Then inspect | -| ---------------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| "Posts do not appear on Mastodon" | `01-Process-Map.md` P5/P7 | `04-API-Compatibility.md`, local export tests | -| "Imported replies are wrong or duplicated" | `01-Process-Map.md` P4 | `02-State-Model.md` tombstones/rechecks, reply tests | -| "Disable comment/reply synchronization" | `01-Process-Map.md` P2b/P4/P4a/P6/P9/P10 | `02-State-Model.md` comment/reply gate lifecycle, `06-Process-Flow.md` gate diagrams, regression tests | -| "Require visitor approval before exporting comments to Mastodon" | `01-Process-Map.md` P2c/P6/P10 | `02-State-Model.md` `comment_reply_optins`, direct path without CommentCenter, moderated pending-file lifecycle, rejection/orphan cleanup, authenticated grants, credential-deferred opt-ins, `06-Process-Flow.md` opt-in diagram and regression tests | -| "Deletion is unsafe or incomplete" | `01-Process-Map.md` P4/P9 | Immediate tombstones, import-boundary fallback, remote-ID ownership, delete endpoint fallback and partial-sync tests | -| "State repair or diagnostics are needed" | `02-State-Model.md` maintenance sections | Admin maintenance fieldset, `mastodon-state-cli.php` | -| "Admin page reports stale or wrong state" | P12 and scheduler state | State summary and admin assignment tests | -| "Large sites are slow" | P1, budgets and scheduler state | Large-state and rate-limit tests | -| "Mastodon API behavior changed" | `04-API-Compatibility.md` | endpoint matrix, capability helpers, targeted tests | -| "Comment author links open new tabs only for external URLs" | `01-Process-Map.md` P14 | `comments.tpl`, `modifier.is_external_url.php`, target-decision test including root-install same-host paths | -| "Imported status source links open the single toot in a new tab" | `01-Process-Map.md` P3, `04-API-Compatibility.md` | `plugin_mastodon_imported_status_footer_bbcode()`, status-footer target tests | +| Requested change | Start reading | Then inspect | +| ---------------------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| "Posts do not appear on Mastodon" | `01-Process-Map.md` P5/P7 | `04-API-Compatibility.md`, local export tests | +| "Mastodon rejects a post as over 500 characters when URLs are present" | `04-API-Compatibility.md` status limits | `plugin_mastodon_status_text_url_spans()`, `plugin_mastodon_status_text_length()`, URL punctuation-budget regression test | +| "Imported replies are wrong or duplicated" | `01-Process-Map.md` P4 | `02-State-Model.md` tombstones/rechecks, reply tests | +| "Disable comment/reply synchronization" | `01-Process-Map.md` P2b/P4/P4a/P6/P9/P10 | `02-State-Model.md` comment/reply gate lifecycle, `06-Process-Flow.md` gate diagrams, regression tests | +| "Require visitor approval before exporting comments to Mastodon" | `01-Process-Map.md` P2c/P6/P10 | `02-State-Model.md` `comment_reply_optins`, direct path without CommentCenter, moderated pending-file lifecycle, rejection/orphan cleanup, authenticated grants, credential-deferred opt-ins, `06-Process-Flow.md` opt-in diagram and regression tests | +| "Deletion is unsafe or incomplete" | `01-Process-Map.md` P4/P9 | Immediate tombstones, import-boundary fallback, remote-ID ownership, delete endpoint fallback and partial-sync tests | +| "State repair or diagnostics are needed" | `02-State-Model.md` maintenance sections | Admin maintenance fieldset, `mastodon-state-cli.php` | +| "Admin page reports stale or wrong state" | P12 and scheduler state | State summary and admin assignment tests | +| "Large sites are slow" | P1, budgets and scheduler state | Large-state and rate-limit tests | +| "Mastodon API behavior changed" | `04-API-Compatibility.md` | endpoint matrix, capability helpers, targeted tests | +| "Comment author links open new tabs only for external URLs" | `01-Process-Map.md` P14 | `comments.tpl`, `modifier.is_external_url.php`, target-decision test including root-install same-host paths | +| "Imported status source links open the single toot in a new tab" | `01-Process-Map.md` P3, `04-API-Compatibility.md` | `plugin_mastodon_imported_status_footer_bbcode()`, status-footer target tests | ## Compatibility target for maintainers @@ -95,7 +96,7 @@ After changing the plugin, the simulation harness or this documentation set, run php fp-plugins/mastodon/developer-docs/check-consistency.php ``` -The checker compares the regression matrix with `simulate_mastodon_plugin.php`, the function organigram with `plugin.mastodon.php`, and the documented OAuth helper names with the current implementation. +The checker compares the regression matrix with `simulate_mastodon_plugin.php`, the function organigram with `plugin.mastodon.php`, URL-punctuation budgeting documentation, and the documented OAuth helper names with the current implementation. ## Current split-state guardrails diff --git a/fp-plugins/mastodon/developer-docs/check-consistency.php b/fp-plugins/mastodon/developer-docs/check-consistency.php index 4fe81d56..e10efa75 100644 --- a/fp-plugins/mastodon/developer-docs/check-consistency.php +++ b/fp-plugins/mastodon/developer-docs/check-consistency.php @@ -816,6 +816,97 @@ function mastodon_docs_extract_backticked_function_references($content) { } + +$urlPunctuationBudgetRequiredDocs = array( + '00-Mental-Model.md' => array( + $mentalModelDocContent, + array( + 'plugin_mastodon_status_text_url_spans()', + 'sentence punctuation immediately after a link is normal text' + ) + ), + '01-Process-Map.md' => array( + $processMapDocContent, + array( + 'URL-aware text budgeting', + 'punctuation after `https://...`' + ) + ), + '03-Function-Process-Matrix.md' => array( + mastodon_docs_read_file(__DIR__ . '/03-Function-Process-Matrix.md', $errors), + array( + 'plugin_mastodon_status_text_url_spans', + 'URL punctuation-budget regression tests' + ) + ), + '04-API-Compatibility.md' => array( + $apiDocContent, + array( + 'configuration.statuses.characters_reserved_per_url', + 'trailing sentence punctuation such as `,` or `.` outside the URL span' + ) + ), + '05-Regression-Test-Matrix.md' => array( + $regressionDocContent, + array( + 'Mastodon URL budgeting counts trailing punctuation outside the URL placeholder', + 'URL-aware status budgeting at punctuation boundaries' + ) + ), + '06-Process-Flow.md' => array( + $flowDocContent, + array( + 'plugin_mastodon_status_text_url_spans', + 'trim punctuation boundaries' + ) + ), + '07-Function-Organigram.md' => array( + $organigramDocContent, + array( + 'plugin_mastodon_status_text_url_spans()', + 'leave trailing sentence punctuation outside the URL placeholder budget' + ) + ), + 'README.md' => array( + mastodon_docs_read_file(__DIR__ . '/README.md', $errors), + array( + 'Mastodon rejects a post as over 500 characters when URLs are present', + 'URL-punctuation budgeting documentation' + ) + ) +); +foreach ($urlPunctuationBudgetRequiredDocs as $docName => $docData) { + $docContent = (string) $docData [0]; + $requiredSnippets = $docData [1]; + foreach ($requiredSnippets as $requiredSnippet) { + if ($docContent !== '' && strpos($docContent, (string) $requiredSnippet) === false) { + $errors [] = $docName . ' missing URL-punctuation budget documentation snippet: ' . (string) $requiredSnippet; + } + } +} + +$urlPunctuationBudgetPluginSnippets = array( + 'function plugin_mastodon_status_text_url_spans(', + 'plugin_mastodon_status_text_url_spans($text)', + 'Sentence punctuation appended after an URL' +); +foreach ($urlPunctuationBudgetPluginSnippets as $requiredPluginSnippet) { + if ($pluginContent !== '' && strpos($pluginContent, $requiredPluginSnippet) === false) { + $errors [] = 'Plugin missing URL-punctuation budget implementation snippet: ' . $requiredPluginSnippet; + } +} + +$urlPunctuationBudgetSimulationSnippets = array( + 'Mastodon URL budgeting counts trailing punctuation outside the URL placeholder', + '$punctuatedUrlSpans = plugin_mastodon_status_text_url_spans($punctuatedUrlStatus);' +); +foreach ($urlPunctuationBudgetSimulationSnippets as $requiredSimulationSnippet) { + if ($simulationContent !== '' && strpos($simulationContent, $requiredSimulationSnippet) === false) { + $errors [] = 'Simulation missing URL-punctuation budget regression: ' . $requiredSimulationSnippet; + } +} + + $instanceCapabilityRequiredDocs = array( '01-Process-Map.md' => array( $processMapDocContent, diff --git a/fp-plugins/mastodon/plugin.mastodon.php b/fp-plugins/mastodon/plugin.mastodon.php index d199a019..86b1fae5 100644 --- a/fp-plugins/mastodon/plugin.mastodon.php +++ b/fp-plugins/mastodon/plugin.mastodon.php @@ -3,7 +3,7 @@ * Plugin Name: Mastodon * Plugin URI: https://www.flatpress.org * Description: Synchronizes FlatPress entries and comments with Mastodon and provides a Mastodon profile widget. [Instructions] - * Version: 2.8.1 + * Version: 2.8.2 * Author: FlatPress * Author URI: https://www.flatpress.org */ @@ -9690,6 +9690,58 @@ function plugin_mastodon_instance_url_reserved_length($options) { return 23; } +/** + * Extract Mastodon-countable URL spans from a plain-text status. + * + * Mastodon gives the configured URL budget only to URL entities that its formatter + * can parse. Sentence punctuation appended after an URL therefore remains normal + * text and must not disappear into the fixed URL placeholder budget. + * + * @param string $text + * @return array + */ +function plugin_mastodon_status_text_url_spans($text) { + $text = (string) $text; + $spans = array(); + $matches = array(); + if ($text === '' || !preg_match_all('~https?://[^\s<>"\x00-\x1F\x7F]+~iu', $text, $matches, PREG_OFFSET_CAPTURE)) { + return $spans; + } + + foreach ($matches [0] as $match) { + $candidate = (string) $match [0]; + $position = (int) $match [1]; + + do { + $previousCandidate = $candidate; + $candidate = preg_replace('/[.,;:!?]+$/u', '', $candidate); + $candidate = is_string($candidate) ? $candidate : $previousCandidate; + + foreach (array(')' => '(', ']' => '[', '}' => '{') as $closing => $opening) { + while ($candidate !== '' && substr($candidate, -1) === $closing && substr_count($candidate, $closing) > substr_count($candidate, $opening)) { + $candidate = substr($candidate, 0, -1); + } + } + } while ($candidate !== $previousCandidate); + + if ($candidate === '' || !preg_match('~^https?://~i', $candidate)) { + continue; + } + $host = @parse_url($candidate, PHP_URL_HOST); + if (!is_string($host) || trim($host) === '') { + continue; + } + + $spans [] = array( + 'url' => $candidate, + 'position' => $position, + 'length' => strlen($candidate) + ); + } + + return $spans; +} + /** * Calculate the Mastodon-visible length of a plain-text status. * @param string $text @@ -9702,19 +9754,20 @@ function plugin_mastodon_status_text_length($text, $urlReservedLength = 23) { if ($text === '') { return 0; } - if (!preg_match_all('~https?://[^\s<>"]+~iu', $text, $matches, PREG_OFFSET_CAPTURE)) { + $urlSpans = plugin_mastodon_status_text_url_spans($text); + if ($urlSpans === array()) { return function_exists('mb_strlen') ? mb_strlen($text, 'UTF-8') : strlen($text); } $length = 0; $offset = 0; - foreach ($matches [0] as $match) { - $url = (string) $match [0]; - $position = (int) $match [1]; + foreach ($urlSpans as $span) { + $position = (int) $span ['position']; + $urlLength = (int) $span ['length']; $segment = substr($text, $offset, $position - $offset); $length += function_exists('mb_strlen') ? mb_strlen($segment, 'UTF-8') : strlen($segment); $length += $urlReservedLength; - $offset = $position + strlen($url); + $offset = $position + $urlLength; } $tail = substr($text, $offset); $length += function_exists('mb_strlen') ? mb_strlen($tail, 'UTF-8') : strlen($tail); @@ -9745,16 +9798,17 @@ function plugin_mastodon_limit_status_text($text, $limit, $urlReservedLength = 2 $budget = $limit - 1; $result = ''; $consumed = 0; - $matches = array(); - if (!preg_match_all('~https?://[^\s<>"]+~iu', $text, $matches, PREG_OFFSET_CAPTURE)) { + $urlSpans = plugin_mastodon_status_text_url_spans($text); + if ($urlSpans === array()) { return plugin_mastodon_limit_text($text, $limit); } $offset = 0; $truncated = false; - foreach ($matches [0] as $match) { - $url = (string) $match [0]; - $position = (int) $match [1]; + foreach ($urlSpans as $span) { + $position = (int) $span ['position']; + $urlLength = (int) $span ['length']; + $url = substr($text, $position, $urlLength); $segment = substr($text, $offset, $position - $offset); $segmentLength = function_exists('mb_strlen') ? mb_strlen($segment, 'UTF-8') : strlen($segment); $available = $budget - $consumed; @@ -9774,7 +9828,7 @@ function plugin_mastodon_limit_status_text($text, $limit, $urlReservedLength = 2 } $result .= $url; $consumed += $urlReservedLength; - $offset = $position + strlen($url); + $offset = $position + $urlLength; } if (!$truncated) { diff --git a/fp-plugins/mastodon/regression-test/simulate_mastodon_plugin.ph_ b/fp-plugins/mastodon/regression-test/simulate_mastodon_plugin.ph_ index b560be11..e37f3399 100644 --- a/fp-plugins/mastodon/regression-test/simulate_mastodon_plugin.ph_ +++ b/fp-plugins/mastodon/regression-test/simulate_mastodon_plugin.ph_ @@ -3931,8 +3931,9 @@ ob_start(); plugin_mastodon_head(); $headMarkupWithoutUser = trim((string) ob_get_clean()); $allOk = test_result( - 'Mastodon head metadata stays silent when no username is configured', - $headMarkupWithoutUser === '', + 'Mastodon head metadata omits profile metadata when no username is configured', + strpos($headMarkupWithoutUser, ' plugin_mastodon_status_text_length($urlAwareComment, $urlReservedLength) )) ) && $allOk; + +$punctuatedUrlStatus = str_repeat('A', 426) . ' https://forum.flatpress.org/, https://github.com/Fraenkiman https://wiki.flatpress.org/.'; +$punctuatedUrlOverLimit = $punctuatedUrlStatus . ' Zusatz'; +$punctuatedUrlLimited = plugin_mastodon_limit_status_text($punctuatedUrlOverLimit, 500, $urlReservedLength); +$punctuatedUrlSpans = plugin_mastodon_status_text_url_spans($punctuatedUrlStatus); +$allOk = test_result( + 'Mastodon URL budgeting counts trailing punctuation outside the URL placeholder', + $urlReservedLength === 23 + && count($punctuatedUrlSpans) === 3 + && isset($punctuatedUrlSpans [0] ['url'], $punctuatedUrlSpans [2] ['url']) + && $punctuatedUrlSpans [0] ['url'] === 'https://forum.flatpress.org/' + && $punctuatedUrlSpans [2] ['url'] === 'https://wiki.flatpress.org/' + && plugin_mastodon_status_text_length($punctuatedUrlStatus, $urlReservedLength) === 500 + && plugin_mastodon_limit_status_text($punctuatedUrlStatus, 500, $urlReservedLength) === $punctuatedUrlStatus + && $punctuatedUrlLimited !== $punctuatedUrlOverLimit + && plugin_mastodon_status_text_length($punctuatedUrlLimited, $urlReservedLength) <= 500, + json_encode(array( + 'reserved' => $urlReservedLength, + 'spans' => $punctuatedUrlSpans, + 'base_length' => plugin_mastodon_status_text_length($punctuatedUrlStatus, $urlReservedLength), + 'limited_length' => plugin_mastodon_status_text_length($punctuatedUrlLimited, $urlReservedLength), + 'limited' => $punctuatedUrlLimited + )) +) && $allOk; $GLOBALS ['plugin_mastodon_test_http_requests'] = array(); if (simulate_request_flag('live-auth')) {