Thanks for considering a contribution. This is a one-maintainer project, so the contributor's biggest help is keeping changes small, focused and covered by the existing quality gates — rebasing a sprawling PR is what actually slows things down.
- Fork the repository.
- Create a feature branch (
git checkout -b feature/my-featureorfix/short-bug-name). - Make your changes, ideally with a test that fails before and passes after. See Testing.
- Run the full quality-gate locally before pushing — see Quality gates.
- Commit with a concise message that explains the why
(
git commit -m "Fix: retry counter double-bumps on deadline errors"). - Push and open a Pull Request against
main. CI will run the same gates on PHP 8.2 / 8.3 / 8.4.
Requirements:
- PHP 8.2+ with the
opensslextension (PHP 8.2, 8.3, 8.4 are the supported matrix). - Composer 2.x.
- A WordPress + UpdraftPlus + Seafile test environment for end-to-end verification. A Docker smoke-test harness is maintained separately — ask if you need access.
git clone https://github.com/malziland/Seafile-Updraft-Backup-Uploader.git
cd Seafile-Updraft-Backup-Uploader
composer install # pulls phpunit 11, phpcs, phpstan, brain/monkey etc.Every push and pull request must pass three gates. Run them locally before opening a PR — red CI is a stop-the-line signal.
./vendor/bin/phpunitThe suite is PHPUnit 11 with Brain\Monkey for WordPress function mocking.
Tests live in tests/unit/ and use PHP 8 attributes (#[CoversClass] /
#[CoversMethod]) — not legacy @covers docblocks.
When fixing a bug, add a test that fails against the pre-fix code and passes with the fix. When adding a feature, cover the contract, not the implementation — the tests should survive refactors of the code under test.
./vendor/bin/phpcs --extensions=php \
--ignore=vendor,languages,assets,tests,.github,scripts .Reasoned exclusions (short array syntax, Yoda conditions, several
docblock sniffs) live in phpcs.xml.dist. Security-relevant sniffs stay
active.
./vendor/bin/phpstan analyse --level=5 --memory-limit=1G --no-progressLevel 5 with szepeviktor/phpstan-wordpress for WordPress function
signatures. If you need to narrow a type that PHPStan can't prove, prefer a
short @phpstan-* docblock at the method level over a @phpstan-ignore-line.
- Follow the WordPress Coding Standards
modulo the relaxations in
phpcs.xml.dist. - Text domain for every user-facing string:
seafile-updraft-backup-uploader. - Use
__(),esc_html__(),esc_attr__()(etc.) — never concatenate variables into translatable strings; usesprintf()with a/* translators: %s is ... */comment. - PHPDoc on public and private methods is encouraged but not mandatory. Prefer comments that explain why, not what — the what is visible in the code itself.
- When adding a new option, constant or AJAX endpoint, update ARCHITECTURE.md in the same PR.
Before submitting a PR that touches the upload or restore paths, please verify manually:
- Plugin activation / deactivation leaves no orphaned options.
- Settings save and reload correctly.
- Connection test succeeds with valid Seafile credentials.
- Small-file upload works (single chunk).
- Large-file chunked upload works (file larger than the configured chunk).
- Pause / Resume preserves the exact byte offset.
- Worker-crash recovery (since 1.0.7): if your change touches
detect_worker_crash_and_defer()orprocess_queue_tick(), simulate a stuck chunk (e.g. dropSBU_TIMEOUT_UPLOADto 1 s on a large file). Expected sequence in the activity log: WARNUNG "Worker still abgestürzt …" → second WARNUNG with "an gleicher Stelle — Chunk-Größe auf X MB reduziert" → eventually FEHLER "Datei nach wiederholten Worker-Abstürzen übersprungen" with the queue advancing to the next file. Verifyfile_idxanderrwere incremented andwpcore.zip(or whatever the next file is) gets uploaded. - Retention management deletes old backups on the Seafile side.
- E-mail notifications are sent when
notifyis set toerrororalwaysand fire for the right cases. - Dashboard widget displays the latest status.
- Backup list loads and renders type badges.
- Full restore from Seafile works (the only restore path; single-file downloads were removed in 1.0.6).
- Delete on Seafile works.
- Plugin uninstall removes all options.
If your change doesn't touch those paths, say so in the PR description — please don't paste a blank checklist.
Do not file security issues as public GitHub issues. See SECURITY.md for the responsible-disclosure process.
Source language: German. All __()-wrapped strings in the source are
German; German users need no translation file and fall back to the source
strings directly.
Translation files live in /languages/. The template is regenerated from
source whenever user-facing strings change:
./scripts/regen-pot.sh # regenerates .pot, merges into any existing .po, compiles .mo
./scripts/check-i18n.sh # CI-style smoke test; run before releasingTo add a new language (e.g. English):
- Run
./scripts/regen-pot.shto make sure the template is current. cp languages/seafile-updraft-backup-uploader.pot languages/seafile-updraft-backup-uploader-en_US.po- Translate
msgstr ""entries. - Re-run
./scripts/regen-pot.sh— it compiles.mofor any.poin the folder. - Submit a Pull Request.
- Every user-facing string must be wrapped in
__(),_e(),esc_html__(),esc_html_e(),esc_attr__(), oresc_attr_e()with the text domainseafile-updraft-backup-uploader. - Variables in messages: use
sprintf( __( '... %s ...', 'seafile-updraft-backup-uploader' ), $var )with a/* translators: %s is ... */comment above. - Do not derive the translations folder from
__FILE__inside/includesclasses — useSBU_SLUG . '/languages'. The legacydirname( plugin_basename( __FILE__ ) )pattern silently breaks when the caller is not the main plugin file; the i18n smoke test flags any regression. wp_send_json_error( 'ok' )and'OK'are whitelisted protocol sentinels, not user-facing text. Everything else needs wrapping.
Releases are cut by the maintainer after the quality gates pass on main.
The full workflow:
-
Update
CHANGELOG.mdwith a German entry under a new## <version>heading. Group bullets by Neu, Verbessert, Behoben, Breaking where applicable. -
Update
readme.txt— bumpStable tag, add a matching Upgrade-Notice block and a Changelog entry in German. -
Bump
Version:inseafile-updraft-backup-uploader.phpand theSBU_VERconstant. -
Build the installable ZIP. The plugin is distributed via
Plugins → Uploadin WP Admin, which requires the archive to contain exactly one top-level folder named like the slug (seafile-updraft-backup-uploader/). Stage the runtime files into a temp directory withrsync, excluding dev artefacts, then zip:VERSION=1.0.x TMP=$(mktemp -d) rsync -a \ --exclude='.git' --exclude='.github' --exclude='.claude' \ --exclude='vendor' --exclude='tests' --exclude='scripts' \ --exclude='composer.*' --exclude='phpunit.xml*' \ --exclude='phpcs.xml*' --exclude='phpstan.neon*' \ --exclude='*.zip' --exclude='.DS_Store' \ ./ "$TMP/seafile-updraft-backup-uploader/" (cd "$TMP" && zip -rq "seafile-updraft-backup-uploader-${VERSION}.zip" \ "seafile-updraft-backup-uploader") mv "$TMP/seafile-updraft-backup-uploader-${VERSION}.zip" ./
-
Commit, push, then tag:
git tag v<version> && git push --tags. -
Create the GitHub Release (
gh release create v<version> seafile-updraft-backup-uploader-<version>.zip) — the download links fromreadme.txtandREADME.mdpoint at the tagged release.
CI blocks merges on a red quality-gate; please do not push a tag until main
is green on GitHub.