|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file is the single source of truth for coding agents working in this repository (`AGENTS.md` points here). |
| 4 | + |
| 5 | +## About |
| 6 | + |
| 7 | +Multisite Language Switcher (MSLS) is a WordPress plugin that adds multilingual support to WordPress multisite installations. It connects content (posts, pages, custom post types, taxonomies) across sites in a multisite network for language switching. |
| 8 | + |
| 9 | +## Commands |
| 10 | + |
| 11 | +### Testing |
| 12 | +```bash |
| 13 | +composer phpunit # Run PHPUnit test suite |
| 14 | +composer phpunit -- --filter=TestClassName # Run a single test class |
| 15 | +composer phpunit -- --filter=testMethodName # Run a single test method |
| 16 | +composer phpunit:clover # Run tests with code coverage (XML) |
| 17 | +composer phpunit:html # Run tests with code coverage (HTML) |
| 18 | +``` |
| 19 | + |
| 20 | +### Static Analysis & Linting |
| 21 | +```bash |
| 22 | +composer phpstan # PHPStan at level 8 |
| 23 | +composer phpcs # PHP compatibility check (7.4+) |
| 24 | +vendor/bin/phpcs # WordPress coding standards (uses .phpcs.xml.dist) |
| 25 | +``` |
| 26 | + |
| 27 | +### Building |
| 28 | +```bash |
| 29 | +npm run build # Build JS (uglify + less + Gutenberg block) |
| 30 | +npm run build-msls-block # Build only the Gutenberg block |
| 31 | +``` |
| 32 | + |
| 33 | +### E2E Tests |
| 34 | +```bash |
| 35 | +npx playwright test # Run Playwright tests (against msls.co) |
| 36 | +npx playwright test --ui # Run with UI |
| 37 | +``` |
| 38 | + |
| 39 | +### Local Development Environment |
| 40 | +```bash |
| 41 | +npx wp-env start # Start WordPress multisite via wp-env (PHP 8.3) |
| 42 | +npx wp-env stop |
| 43 | +``` |
| 44 | + |
| 45 | +## Architecture |
| 46 | + |
| 47 | +### Repository Layout |
| 48 | +- `MultisiteLanguageSwitcher.php` — plugin bootstrap |
| 49 | +- `includes/` — core PHP classes |
| 50 | +- `src/` — JavaScript source components |
| 51 | +- `assets/` — CSS, JS, flags, images |
| 52 | +- `docs/` — developer reference (API, hooks, snippets) |
| 53 | +- `tests/` — PHPUnit and Playwright tests |
| 54 | + |
| 55 | +### Namespace & Autoloading |
| 56 | +- PSR-4: `lloc\Msls\` maps to `includes/`, split into per-concern sub-namespaces: `Admin\`, `Blog\`, `Cli\`, `Component\`, `ContentImport\`, `ContentTypes\`, `Data\`, `Db\`, `Frontend\`, `Link\`, `Options\`, `Registry\`, `Request\`, `RestApi\` |
| 57 | +- PSR-4 (dev): `lloc\MslsTests\` maps to `tests/phpunit/` |
| 58 | +- Plugin bootstrap: `MultisiteLanguageSwitcher.php` — defines constants, then on `plugins_loaded` requires `includes/aliases.php`, `includes/deprecated.php`, and `includes/api.php`, builds the PHP-DI container from `config.php`, and calls `lloc\Msls\Plugin::init()` plus `lloc\Msls\Cli\Cli::init()` |
| 59 | +- **Backwards-compatibility aliases**: `includes/aliases.php` registers the ~60 pre-3.0 flat class names (`MslsOptions`, `MslsLink`, `MslsPlugin`, …) as `class_alias()` entries for their namespaced replacements. Write new code against the namespaced names; the aliases exist only for third-party consumers |
| 60 | + |
| 61 | +### Key Patterns |
| 62 | +- **Registry/Singleton**: `Registry\Instance` is the base class providing the `::instance()` static accessor (backed by `Registry\Registry`); `Registry\GetSet` extends it to add overloaded property access |
| 63 | +- **Factory methods**: `Options\Options::create()`, `Options\Tax\Tax::create()`, `Options\Query\Query::create()`, `ContentTypes\ContentTypes::create()` return context-aware instances based on WordPress conditional tags (is_category, is_tag, is_day, etc.) |
| 64 | +- **Options hierarchy**: `Options\Options` (base, extends `GetSet`) → `Options\Post\Post` (post translations) / `Options\Tax\Tax` → `Options\Tax\Term` → `Options\Tax\Category` (taxonomy translations) / `Options\Query\Query` → `Author`, `Day`, `Month`, `Year`, `PostType` (archive pages) |
| 65 | +- **Link rendering**: `Link\Link` base class with variants (`Link\TextOnly`, `Link\ImageOnly`, `Link\TextImage`) — selected by the display index 0–3 from `Link\Link::get_types()`, controlled by admin settings |
| 66 | +- **Content Import**: `ContentImport/` subsystem handles duplicating content across sites with importers for post fields, meta, terms, attachments, and thumbnails |
| 67 | +- **REST API / Quick Create**: `RestApi/` exposes the endpoints behind the editor metabox button and the "Add from Translation" submenu (`Admin\TranslationPicker\`) |
| 68 | + |
| 69 | +### Global API Functions |
| 70 | +`includes/api.php` exposes the template functions: `msls_the_switcher()`, `msls_get_switcher()`, `msls_get_permalink()`, `msls_get_flag_url()`, `msls_blog_collection()`, etc. Legacy names (`the_msls()`, `get_the_msls()`, …) live in `includes/deprecated.php` and forward to them with a `_deprecated_function()` notice. |
| 71 | + |
| 72 | +### Developer Documentation |
| 73 | +`docs/` holds the reference material: `api.md` (public API functions), `hooks.md` (every action and filter), `snippets.md` (integration recipes), `acknowledgements.md` (credits and translators). Keep these in sync when adding or renaming a hook or an API function. |
| 74 | + |
| 75 | +### Test Framework |
| 76 | +- PHPUnit 10 with Brain\Monkey for WordPress function mocking |
| 77 | +- Patchwork for redefining PHP internals (`filter_input`, `filter_input_array`, `filter_has_var`) |
| 78 | +- Base test class: `MslsUnitTestCase` — sets up Monkey, stubs common WP escaping/i18n functions |
| 79 | +- Tests mirror the source structure with a `Test` prefix: `includes/Options/Tax/Term.php` → `tests/phpunit/Options/Tax/TestTerm.php` |
| 80 | + |
| 81 | +## CI |
| 82 | + |
| 83 | +GitHub Actions runs PHPStan, PHPCS, PHPUnit, and Playwright on every pull request. |
| 84 | + |
| 85 | +## Conventions |
| 86 | + |
| 87 | +- WordPress Coding Standards enforced via PHPCS (tabs, Yoda conditions, WordPress function spacing) |
| 88 | +- All classes use `declare(strict_types=1)` |
| 89 | +- Text domain: `multisite-language-switcher` everywhere — in the plugin header and in every `__()` / `esc_html__()` call. Do not use `msls` as a text domain; it is the name of the plugin's option row (`get_option( 'msls' )`) |
| 90 | +- Do not modify the plugin header in `MultisiteLanguageSwitcher.php` |
| 91 | +- Do not edit `vendor/`, `build/`, `node_modules/`, or language files directly |
0 commit comments