PHPackages                             ycloudyusa/yusaopeny\_ymca360 - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. ycloudyusa/yusaopeny\_ymca360

ActiveDrupal-module[Utility &amp; Helpers](/categories/utility)

ycloudyusa/yusaopeny\_ymca360
=============================

YUSA OpenY YMCA360 integration

2.2.0(2w ago)02.0k↑1735.7%4[1 issues](https://github.com/YCloudYUSA/yusaopeny_ymca360/issues)[3 PRs](https://github.com/YCloudYUSA/yusaopeny_ymca360/pulls)1PHP

Since Oct 2Pushed 2w agoCompare

[ Source](https://github.com/YCloudYUSA/yusaopeny_ymca360)[ Packagist](https://packagist.org/packages/ycloudyusa/yusaopeny_ymca360)[ RSS](/packages/ycloudyusa-yusaopeny-ymca360/feed)WikiDiscussions 2.x Synced 2d ago

READMEChangelog (9)DependenciesVersions (13)Used By (1)

YMCA360 Integration
===================

[](#ymca360-integration)

🇺🇦
--

[](#)

This module is maintained by Ukrainian developers. Please consider [supporting Ukraine](https://supportukrainenow.org) in a fight for their freedom and the safety of Europe.
Pulls YMCA360 schedule occurrences (in-studio + live stream) into the [YMCA Website Services](https://github.com/YCloudYUSA/yusaopeny) Program Event Framework as `session` nodes, using a windowed, reconciliation-based syncer.

- 🔍 Read our [getting-started instructions](https://github.com/YCloudYUSA/yusaopeny#installation)
- 📖 Search the [documentation](https://ds-docs.y.org/docs/)
- 🤝 Review [community resources](https://ds-docs.y.org/community/)

Table of contents
-----------------

[](#table-of-contents)

- [Highlights](#highlights)
- [Requirements](#requirements)
- [Installation](#installation)
- [Configuration](#configuration)
- [How it works](#how-it-works)
- [Drush commands](#drush-commands)
- [Upgrading](#upgrading)
- [Development](#development)
- [Maintainers](#maintainers)

Highlights
----------

[](#highlights)

**Windowed sync**Native API filters (`start_at`, `end_at`, `scheduled_from`) — fetches only the next N days, no client-side cap, no unbounded paging.**Source-of-truth reconciliation**Mappings missing from the current extract are flagged as orphans and removed. Cap (`max_deletes_per_run`, default 500) protects against misconfigurations.**Empty-extract circuit breaker**Skips orphan reconciliation until N consecutive empty extracts confirm emptiness — prevents a single API blip from wiping the schedule.**Trash-aware deletes**Deletes are wrapped in `trash.manager->executeInTrashContext('ignore', …)` so reconciliation performs real deletes instead of soft-trashing.**Configurable canceled UX**Canceled occurrences stay published with a `CANCELED:` prefix by default, mirroring the Y360 app behaviour.**Optional theming hooks**Writes `field_session_status` and `field_session_original_instructor` when the `session` bundle exposes them, letting themes apply strikethrough or substitute-instructor labels.**Drush surface**`y360:status`, `y360:sync`, `y360:reset-hashes`, `y360:ping` — operators no longer need `php:eval`.Requirements
------------

[](#requirements)

RequirementVersionDrupal core`^11`YMCA Website Services (`yusaopeny`)`11.x`[`drupal/trash`](https://www.drupal.org/project/trash)optional, recommendedNote

When the `trash` module is present, the syncer detects `trash.manager` at runtime and bypasses soft-delete during reconciliation only — user-initiated deletes of other bundles continue to be captured by Trash.

Installation
------------

[](#installation)

```
composer require ycloudyusa/yusaopeny_ymca360
drush en yusaopeny_ymca360 yusaopeny_ymca360_instudio -y
drush updb -y
```

The submodule's install hook adds `yusaopeny_ymca360_instudio.syncer` to `ymca_sync.settings.active_syncers` and (when the `trash` module is enabled) removes syncer-owned bundles from `trash.settings.enabled_entity_types.node` so reconciliation deletes are real.

**Update hooks** (run on existing installs after upgrade)HookPurpose`yusaopeny_ymca360_instudio_update_10001`Backfills the new sync settings (`window_days`, `page_size`, `max_deletes_per_run`, `canceled_title_prefix`, `canceled_publish_behavior`) when missing.`yusaopeny_ymca360_instudio_update_10003`Backfills `sync.window_offset_days = 1` so already-started occurrences stay in the window.`yusaopeny_ymca360_instudio_update_10002`Removes the syncer-owned bundles (`session`, `activity`, `class`, `program`, `program_subcategory`) from `trash.settings.enabled_entity_types.node` if Trash is enabled and tracks nodes. Other bundles the site has opted into Trash for are preserved.Configuration
-------------

[](#configuration)

### `Administration » YMCA Website Services » Integrations » YMCA360`

[](#administration--ymca-website-services--integrations--ymca360)

- **Credentials &amp; Schedules** — API credentials and the `schedule_id` allow-list (Group Fitness, Pool, Racquetball, etc.).
- **Locations mapping** — maps Y360 branches and studios onto site location nodes.

### `Administration » YMCA Website Services » Integrations » YMCA360 In-Studio`

[](#administration--ymca-website-services--integrations--ymca360-in-studio)

**Automatic Sync**SettingDefaultNotes`enable_cron`offWhen on, the submodule's `hook_cron` runs the syncer on every Drupal cron tick.**Sync Window**SettingDefaultNotes`window_days``14`How many days ahead of `now` to pull. Combined with `window_offset_days`, the extracted window is `[now - window_offset_days, now + window_days]`.`window_offset_days``1`How many days *before* `now` to include. Keeps occurrences that already started but have not yet ended (e.g. an Open Swim 5am–5pm at 11am) inside the window so reconciliation does not delete them as orphans. Set to `0` to revert to the pre-2.x behaviour where any occurrence with `start_at < now` is dropped immediately. Increase if you run multi-day occurrences.`page_size``500`Items per API page. Larger pages = fewer requests but more memory per request.`max_deletes_per_run``500`Hard cap on deletions per sync cycle. Excess is logged and deferred to subsequent runs. Set to `0` to disable.`empty_extract_threshold``2`How many consecutive empty extracts must occur before reconciliation runs against an empty set. Lower it to `1` to disable the circuit breaker.**Canceled sessions**SettingDefaultNotes`canceled_title_prefix``CANCELED: `Prepended to the session title when the occurrence is canceled. Empty string = no prefix.`canceled_publish_behavior``keep_published`One of `keep_published` (canceled stays visible with prefix), `follow_api` (respects API `published` flag), `always_unpublish` (hides canceled).Tip

After changing `canceled_publish_behavior` or `canceled_title_prefix`, run `drush y360:reset-hashes` so the next sync rewrites session nodes instead of skipping them via the hash no-op path.

How it works
------------

[](#how-it-works)

 ```
flowchart LR
    API([YMCA360 API]) --> E[Extractor]
    E -->|items| T[Transformer]
    T -->|create / update / delete| L[Loader]
    L -->|session nodes| D[(Drupal)]
    L -->|y360_mapping| D
    E -.->|circuit breaker streak| KV[(keyvalue)]
```

      Loading - **Extractor** builds the `[now - window_offset_days, now + window_days]` window and calls `Y360Client::getSchedulesWindowed()` with the API's native filters. Pagination is API-driven. Tracks consecutive empty extracts in the `yusaopeny_ymca360_syncer` keyvalue collection and toggles `DataWrapper::skipOrphanReconciliation` until emptiness is confirmed.
- **Transformer** classifies the working set:
    - `status=deleted` → existing mapping queued for delete.
    - hash matches existing mapping → no-op (unchanged item).
    - hash differs → update.
    - no existing mapping → create.
    - mappings whose `y360id` is missing from the current extract → orphan, queued for delete (capped, may be skipped by the circuit breaker).
- **Loader** creates or updates `session` nodes via `applySessionFields()` (title with optional cancel prefix, class/activity, time paragraph, location, room, instructor, description, ages, optional fields). Publish state derives from `isPublishedSession()`. Deletes are wrapped in `trash.manager->executeInTrashContext('ignore', …)`.

The hash on each `y360_mapping` row is `md5(serialize($apiItem))` so any field change in the upstream item triggers an update on the next sync.

Note

Under the hood the module uses [YMCA Sync](https://github.com/YCloudYUSA/ymca_sync). When the syncer runs on Drupal cron, run cron often (e.g. every 15 minutes). Sites that prefer an explicit job runner can wire one with `drush y360:sync`.

Drush commands
--------------

[](#drush-commands)

CommandAliasPurpose`drush y360:status``y360-st`Snapshot of mapping counts (total, in-window, past, future, blank-hash), key config values, last cron run.`drush y360:sync [--syncer=…]``y360-sync`Runs the syncer once. Defaults to `yusaopeny_ymca360_instudio.syncer`.`drush y360:reset-hashes``y360-rh`Clears `hash` on every mapping row so the next sync re-applies session fields.`drush y360:ping``y360-ping`Pings the YMCA360 API and prints a one-line OK / failure message.Upgrading
---------

[](#upgrading)

Important

**`2.0.0` is a breaking release.** Drupal 10 support is dropped (`core_version_requirement: ^11`). The legacy `Y360Cleaner` service, the `yusaopeny_ymca360_cron` hook, and the `drush y360:cleanup` command have been removed — reconciliation now handles past mappings as part of every sync run.

**1.x → 2.0.0 checklist**- Site is on Drupal 11.
- `composer require ycloudyusa/yusaopeny_ymca360:^2.0`
- `drush updb -y` — applies `update_10001` (settings backfill) and `update_10002` (Trash bundle exclusion).
- Verify `drush y360:status` shows the expected window and counts.
- Run `drush y360:sync` once and check the watchdog channel `yusaopeny_ymca360_syncer`.
- If you maintained a custom `ExtractorBase` subclass, update its constructor — a `KeyValueFactoryInterface` argument was added.

Development
-----------

[](#development)

The module ships with no `version:` field in `composer.json`. Composer resolves the package version from the latest git tag — keep it that way and tag releases off `2.x`.

The pipeline is wired through three abstract base services (`yusaopeny_ymca360.{extractor,transformer,loader}`) that submodules extend via `parent:` references in their own `*.services.yml`. See `modules/yusaopeny_ymca360_instudio/yusaopeny_ymca360_instudio.services.yml` for the canonical example.

Logs land in the `yusaopeny_ymca360_syncer` channel; tail with:

```
drush ws --type=yusaopeny_ymca360_syncer --extended
```

Maintainers
-----------

[](#maintainers)

- [Andrii Podanenko (`podarok`)](https://www.drupal.org/u/podarok)
- [Vlad Sadretdinov (`svicervlad`)](https://github.com/svicervlad)
- [Andrey Maximov (`andreymaximov`)](https://www.drupal.org/u/andreymaximov)
- [Five Jars](https://fivejars.com/)

Issues and pull requests live at [https://github.com/YCloudYUSA/yusaopeny\_ymca360](https://github.com/YCloudYUSA/yusaopeny_ymca360).

Acknowledgements
----------------

[](#acknowledgements)

The `2.0.0` release — windowed sync, reconciliation, Trash-aware deletes, configurable canceled UX, drush commands — was sponsored by [YMCA of Northern Colorado](https://ymcanoco.org/) and delivered by [ITCare](https://itcare.company/).

###  Health Score

47

—

FairBetter than 93% of packages

Maintenance96

Actively maintained with recent releases

Popularity24

Limited adoption so far

Community20

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 51.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~23 days

Recently: every ~10 days

Total

12

Last Release

17d ago

Major Versions

1.1.x-dev → 2.0.02026-05-01

### Community

Maintainers

![](https://www.gravatar.com/avatar/bf41cdf9f0975a3cc5997aaaff41046eac88c85c7dbcd8410b8b09e35f55c11d?d=identicon)[danylevskyi](/maintainers/danylevskyi)

![](https://www.gravatar.com/avatar/1388b312dca2d4c24fb3474c71a6729b13b255860ff0188b0286013bc703bf5a?d=identicon)[podarok](/maintainers/podarok)

![](https://avatars.githubusercontent.com/u/26228931?v=4)[Vlad](/maintainers/svicervlad)[@svicervlad](https://github.com/svicervlad)

---

Top Contributors

[![svicervlad](https://avatars.githubusercontent.com/u/26228931?v=4)](https://github.com/svicervlad "svicervlad (30 commits)")[![AndreyMaximov](https://avatars.githubusercontent.com/u/5453109?v=4)](https://github.com/AndreyMaximov "AndreyMaximov (10 commits)")[![podarok](https://avatars.githubusercontent.com/u/563412?v=4)](https://github.com/podarok "podarok (8 commits)")[![froboy](https://avatars.githubusercontent.com/u/238201?v=4)](https://github.com/froboy "froboy (6 commits)")[![andreyzb](https://avatars.githubusercontent.com/u/2608621?v=4)](https://github.com/andreyzb "andreyzb (4 commits)")

### Embed Badge

![Health badge](/badges/ycloudyusa-yusaopeny-ymca360/health.svg)

```
[![Health](https://phpackages.com/badges/ycloudyusa-yusaopeny-ymca360/health.svg)](https://phpackages.com/packages/ycloudyusa-yusaopeny-ymca360)
```

###  Alternatives

[phasync/phasync

phasync asyncio library for PHP, providing seamless and efficient coroutines via PHP fibers

2463.2k4](/packages/phasync-phasync)[ehaerer/paste-reference

Paste Reference - offers the option to paste references instead of copies for content elements in TYPO3

19367.1k](/packages/ehaerer-paste-reference)[haringsrob/livewire-datepicker

A standalone livewire datepicker component without dependencies

4118.1k](/packages/haringsrob-livewire-datepicker)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
