Repository navigation
Conversation
First step of the #1788 pilot. A network admin picks a Meetup group and a group site, previews, then imports upcoming events and venues as draft GatherPress events for the organizer to review. Reuses the existing Meetup_Client and the Network Settings > Meetup OAuth connection, so there are no new credentials. Events are written through the same helpers as the frontend's draft save, so they behave like hand-made drafts. Events and venues are keyed on their Meetup IDs: reruns refresh drafts and leave published or trashed events to the organizer. Nothing is published and no emails are sent. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Covers more of #1788's acceptance criteria in the same tool. Past events (opt-in) are published as the group's history, with Meetup's RSVP and attendance counts kept as meta. Their first-publish notification is marked as handled, so publishing them emails nobody. Members (opt-in) are only matched to existing WordPress.org accounts by email. A match joins as a Member with this group's event emails off, since GatherPress opts users in by default. Nobody else gets an account. Meetup member IDs are remembered per group, so someone who leaves isn't re-added. Imports take a network-wide lock: events are looked up and then inserted, so two overlapping imports could otherwise create duplicates. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ports Featured images: each event's Meetup photo becomes its featured image. Only HTTPS URLs on Meetup's image host are downloaded, a shared cover photo is downloaded once per group site, and a draft refresh never replaces an image the organizer picked. Shared connection: the Groups network can't hold its own Meetup token, because the OAuth redirect URI points at WordCamp Central. On Groups, the token option now reads from and writes to Central's, so the importer uses the Meetup Pro account Central is already connected as, and both networks refresh one token instead of invalidating each other's copies. Resumable imports: a run that dies partway (e.g. a timeout) can be retried safely. Meetup IDs are saved with the event and venue posts themselves; past events are published only once complete, via a pending marker the next run finishes; members are opted out of emails before they join; and the import lock is released at shutdown, and only by the request that holds it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Group details fill whatever the site doesn't have yet: the location (the same site meta the group settings tab writes), the About draft seeded by Add Group Site while it's untouched, and the site icon from Meetup's group photo. The source group and its founding date are recorded too. With "match people by email" ticked, Meetup organizers and co-organizers get the Organizer role and assistant/event organizers the Event Organizer role. Imports only ever promote and never grant administrator, and the Meetup account the connection belongs to is skipped, since Pro networks list it as an organizer of every group. Event hosts are added to the group if needed and credited on their events. RSVPs become real GatherPress RSVPs, with "attended" checked in, without joining anyone to the group or sending confirmations. Meetup's waiting list is imported as attending and left to GatherPress to place: saving waiting_list directly on an event with room trips a bug in GatherPress 0.35's waiting-list promotion. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every import now runs as a job through cron, whatever the group's size, so no import is bounded by a single request. Preview queues a job; one cron run fetches the group from Meetup and works out the preview, which waits for Import. Later runs then write it a batch at a time: group details, organizers, members 200 per run, and events 10 per run, saving the cursor and results after each batch and queueing the next. One job exists at a time, network-wide. Each run takes the import lock, so two runs never work the same job at once; a run that dies is redone by the next, which the importer's idempotency already makes safe. The screen shows the job's preview, progress, or results, re-queues a stalled job when it loads (as the network export does), and has a Cancel button. A batch that was running when the job was cancelled can't write it back. The form now posts to admin-post.php and redirects, like the network export. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved defects affect imported content, organizer edits, departed members, past-event classification, and cancellation safety.
5 open findings
What changed in this PR
Adds a network-admin Meetup migration tool for Groups, supporting the migration scope in #1788.
Changes:
- Adds preview and batched imports for group details, events, and optional people matching.
- Shares Central’s Meetup connection with Groups.
- Adds import and shared-connection tests.
| File | Description |
|---|---|
public_html/wp-content/mu-plugins/groups/tests/test-meetup-shared-connection.php |
Tests shared token handling. |
public_html/wp-content/mu-plugins/groups/tests/test-meetup-import.php |
Tests import behavior and job lifecycle. |
public_html/wp-content/mu-plugins/groups/tests/bootstrap.php |
Loads import modules for tests. |
public_html/wp-content/mu-plugins/groups/meetup-shared-connection.php |
Routes Groups token operations to Central. |
public_html/wp-content/mu-plugins/groups/meetup-import.php |
Implements preview, matching, and batched imports. |
🧠 Review effort: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Past events: `Meetup_Client::get_group_events()` rewrites past statuses to "upcoming", so past events came in as drafts. Ask for Meetup's raw status under an alias and use that. - Descriptions: store them as paragraph blocks. The event editor only loads block content, so plain HTML opened as an empty description. - Edited drafts: a draft the organizer has saved since the import now belongs to them, and reruns leave it alone. Refreshing an untouched draft keeps a meeting link the organizer added. - Departures: one record of everyone an import put in the group, checked by the organizer and host steps too, so nobody who left is re-added. - Job saves: a single conditional UPDATE keyed on the job ID, so a batch can't restore a cancelled job or overwrite a newer one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Took a look into the implementation and spotted two edge cases, addressed with unit tests in #2205:
|
gedex
left a comment
There was a problem hiding this comment.
Tested locally with a 25-event fixture. Preview, import and rerun work, no mail goes out, and a non-network admin is refused.
Please fix the two inline blockers before merge. A rerun overwrites the organizer's About page and promotes demoted organizers back.
Smaller things:
- Past events get the import time as
post_date, so feeds show them as new. - No test fails if you remove the nonce checks.
- No test covers
ensure_member()keeping an existing admin's or editor's role when they host an event.
Couldn't test the real Meetup API, so skipping meetups@wordcamp.org, cancelled events and pagination are still unchecked.
- About page: an untouched seed is one with no revisions. Saving an unpublished draft resets its post_date, so comparing dates let a rerun overwrite the organizer's text. - Organizers: each is handled by an import once. A group that demotes one doesn't see them promoted back by the next import. - Authors: cron runs as user 0, so imported events had no author and no Event Organizer could edit them. They now belong to their first matched host, else the site's lead organizer. - Retries: a job that fails 3 times at the same point is given up on, rather than re-running (and re-fetching from Meetup) forever. A preview waiting for confirmation is never counted against. - Past events are dated when they happened, not when they were imported. - Tests: the handler tests now pass a valid nonce, so they fail if the capability check is removed; separate tests cover the nonce checks. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Thanks @gedex. Fixed the review-summary points in cdfe116 too: past events are dated when they happened, nonce checks have their own tests, and |
|
Thanks @bor0. All good from my side. |
…ptions (#2205) * Groups: support localized Meetup URLs and rich Gutenberg block descriptions * Groups: Adjust list and quote assertions in test_rich_description_blocks_load_in_editor * Groups: Match editor block markup and preserve two-letter group names * Groups: Support nested lists and separator blocks in Meetup descriptions - Convert lists using DOMDocument and DOMXPath to recursively generate valid Gutenberg block markup for nested lists of arbitrary depth. - Convert <hr> tags into core/separator blocks with alpha channel opacity class. - Avoid wrapping block-level elements (table, div, section, etc.) in paragraph tags. - Update unit tests with nested list, separator, and container preservation assertions. * Groups: Format separator tag with self-closing space
GatherPress 0.36 removes `Event::$rsvp`, so the importer got null back and saved no RSVPs. Build `Rsvp` directly, as `rest.php` now does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>


Part of #1788. Adds Network Admin > Groups > Import from Meetup.
Flow
Reruns are safe: published events and anything the organizer changed are left alone. Uses Central's existing Meetup connection.
Full flow, step by step
Before the first import
Preview
3. Open Groups > Import from Meetup (network admins only), enter the Meetup group URL, pick the site, optionally tick past events and matching people, click Preview.
4. The job is queued (
fetching); another import in progress is refused.5. Cron run 1 fetches the group, events and (if matching) organizers and members, and works out the plan: group details set / kept / missing, people who'd join or be promoted, each event created / published / refreshed / skipped. The job becomes
ready. Nothing is written. If Meetup fails, the job becomesfailedwith the reason.6. Reload to see the preview, with Import and Cancel.
Import
7. Click Import. The job becomes
importing; each cron run takes the lock, writes one batch, saves progress, and queues the next:Emails: none. Upcoming events stay drafts, past events are pre-marked as notified, RSVPs are saved by the admin onto drafts, and everyone added has emails off. Publishing a draft later sends the usual new-event email.
When something goes wrong
After
9. The organizer adds meeting links, checks the About page, and publishes.
10. Rerun anytime before cutover: new events are created, drafts refreshed, new matches added; anything the organizer changed stays.
What it brings
What it doesn't bring
Never overwrites anything the site already has or the organizer changed.
Testing
phpunit --group groups: 935/935.Not tested against the live Meetup API yet: next step is a dry run on a sandbox.
🤖 Generated with Claude Code