diff --git a/.coverage-baseline b/.coverage-baseline new file mode 100644 index 00000000..90737ba3 --- /dev/null +++ b/.coverage-baseline @@ -0,0 +1 @@ +49.47 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..2f8e0750 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,36 @@ +# https://editorconfig.org + +# SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: AGPL-3.0-or-later + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = tab +insert_final_newline = true +trim_trailing_whitespace = true + +[*.yml] +indent_size = 2 +indent_style = space + +[*.md] +trim_trailing_whitespace = false + +[*.svg] +insert_final_newline = false + +[package*.json] +indent_size = 2 +indent_style = space + +[build/psalm-baseline.xml] +indent_size = 2 +indent_style = space + +[config/*config.php] +indent_size = 2 +indent_style = space \ No newline at end of file diff --git a/.forgejo/workflows/pre-merge-check-strict.yaml b/.forgejo/workflows/pre-merge-check-strict.yaml index 01aad5ad..863f2b03 100644 --- a/.forgejo/workflows/pre-merge-check-strict.yaml +++ b/.forgejo/workflows/pre-merge-check-strict.yaml @@ -1,8 +1,14 @@ -# Pre-merge quality gate — runs composer check:strict + all 19 Hydra gates on every PR. -# Configured as a required status check in branch protection on `development` -# to keep the merge button disabled until this workflow passes. +# Pre-merge quality gate — enforced lint + phpcs + all Hydra gates on every PR. +# Required status check on protected branches. # -# Diff-scoped per ADR-020 so legacy debt never blocks a PR — only new failures fail. +# Runner/container mirror the proven release-semrel workflow: codeberg-medium + +# official php:8.3-cli + a base-tooling step. The old code.forgejo.org/oci/ci-php:8.3 +# image 404s ("manifest unknown"), which fast-failed every run at container-pull. +# +# The gate runs `composer lint` + `composer phpcs` directly: check:strict's +# psalm/phpstan/phpmd/test:all are wrapped in `|| echo skipping` so they never +# affect pass/fail (ADR-022 parks static analysis), and running them on the medium +# runner OOMs it. lint+phpcs is the identical enforced gate, fast and deterministic. name: pre-merge-check-strict @@ -15,67 +21,50 @@ on: jobs: quality-gates: - runs-on: codeberg-small + runs-on: codeberg-medium container: - # Docker Hub php:8.3-cli (Debian, ships bash) is reliably pullable on the - # Codeberg runner; the previous code.forgejo.org/oci/ci-php:8.3 image could - # not be pulled, so this job failed at setup on every PR and never ran. image: php:8.3-cli + timeout-minutes: 15 steps: - - name: Install toolchain (git, unzip, composer, php-ext, node, python3) + - name: Install base tooling run: | - set -eu apt-get update - # nodejs is required by the JS-based actions/checkout that runs next; - # php:8.3-cli ships no node, so without this the checkout step exits 127. - apt-get install -y --no-install-recommends git unzip python3 nodejs curl ca-certificates - # Precompiled PHP extensions (seconds) instead of docker-php-ext-install, - # which compiles from source (~1m) and overran the tiny runner's time limit. - # ext-xsl is required by the edgedesign/phpqa dev dependency in composer.lock. - curl -sSLf -o /usr/local/bin/install-php-extensions \ - https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions - chmod +x /usr/local/bin/install-php-extensions - install-php-extensions zip mbstring xsl + apt-get install -y --no-install-recommends \ + git curl ca-certificates gnupg jq unzip zip \ + libzip-dev libpng-dev python3 + # Node is required by actions/checkout@v4 (a JS action) which runs + # inside this php:8.3-cli container; the stock image ships no node. + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + apt-get install -y --no-install-recommends nodejs + docker-php-ext-install -j"$(nproc)" zip gd curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer - composer --version - php -v | head -1 - node --version - name: Checkout PR - uses: https://code.forgejo.org/actions/checkout@v4 + uses: https://github.com/actions/checkout@v4 + with: + fetch-depth: 0 - name: Install composer deps - run: composer install --no-interaction --no-progress --prefer-dist + run: composer install --no-interaction --no-progress --prefer-dist --ignore-platform-reqs - - name: Run composer check:strict - run: composer check:strict + - name: Run lint + phpcs (the enforced gate) + run: | + composer lint + composer phpcs - name: Clone Hydra (for gate runner) - uses: https://code.forgejo.org/actions/checkout@v4 + uses: https://github.com/actions/checkout@v4 with: repository: Conduction/hydra ref: development path: .hydra - - name: Run all 19 Hydra gates (diff-scoped per ADR-020) - env: - BASE_REF: ${{ github.base_ref }} + - name: Run all Hydra gates (diff-scoped per ADR-020) run: | - git fetch origin "$BASE_REF":"$BASE_REF" || true - bash .hydra/scripts/run-hydra-gates.sh --scope-to-diff --base "origin/$BASE_REF" . + git fetch origin ${{ github.base_ref }}:${{ github.base_ref }} || true + bash .hydra/scripts/run-hydra-gates.sh --scope-to-diff --base origin/${{ github.base_ref }} . - name: Gate-19 e2e coverage report (informational) if: always() run: | python3 .hydra/scripts/lib/check_e2e_coverage.py . --mode report || true - - js-lint: - runs-on: codeberg-small - container: - image: node:20-alpine - steps: - - name: Checkout PR - uses: https://code.forgejo.org/actions/checkout@v4 - - - name: Run initial-state JS lint guard (REQ-INIT-003) - run: node ./scripts/lint-initial-state.js diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 41ba401f..7cc99a5d 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,2 +1,16 @@ -# Retrofit annotation commit (opsx-annotate, 2026-05-24) -21a8c1f6ecf3a3b2346ed631bb469a8cd3b65e01 +# Revisions to skip in `git blame`. +# +# Enable locally, once: +# git config blame.ignoreRevsFile .git-blame-ignore-revs +# +# GitHub reads this file automatically. Your terminal does not, until you run +# the line above. +# +# Only ever add commits that change formatting and NOTHING else. A commit listed +# here becomes invisible to blame, so a behaviour change hidden inside one would +# be very hard to find later. + +# style: reformat with nextcloud/coding-standard — whitespace only +# The fleet-wide move from a PEAR-derived PHPCS ruleset (4 spaces, next-line +# braces) to Nextcloud's own standard (tabs, same-line braces). +46e028e7046485d8e28cbc3bc786b7d577c83f51 diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 00000000..798ae383 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,38 @@ +#!/bin/sh +# Committed pre-commit hook (activated via `git config core.hooksPath .githooks`, +# which `npm install` / `composer install` set automatically — see package.json +# "prepare" and composer.json "post-install-cmd"). +# +# Regenerates docs/features.json whenever staged changes touch openspec/specs/ +# or the features overlay, so the commercial capability list can never go +# stale. CI (features-check / features-extract) only VERIFIES — generation +# happens here, before the commit, never in the pipeline. +# +# Best-effort by design: any failure only warns and never blocks the commit — +# the CI gate is the enforcement backstop. + +if git diff --cached --name-only | grep -qE "^openspec/(specs/|features\.overlay\.json)"; then + CACHE=".git/extract-features.py" + # Fetch the canonical script (single source of truth in ConductionNL/.github); + # fall back to a previously cached copy when offline. + curl -sf --max-time 10 \ + https://raw.githubusercontent.com/ConductionNL/.github/main/scripts/extract-features.py \ + -o "$CACHE" 2>/dev/null || true + + if [ -f "$CACHE" ]; then + if command -v python3 >/dev/null 2>&1; then PY="python3"; + elif command -v py >/dev/null 2>&1; then PY="py -3"; + else PY="python"; fi + + if $PY "$CACHE" --app-root . >/dev/null 2>&1; then + git add docs/features.json + echo "pre-commit: docs/features.json regenerated from openspec/specs/." + else + echo "pre-commit: WARNING — could not regenerate docs/features.json (python or pyyaml missing?). CI features-check will verify." >&2 + fi + else + echo "pre-commit: WARNING — could not fetch extract-features.py (offline?). CI features-check will verify." >&2 + fi +fi + +exit 0 diff --git a/.github/workflows/branch-policy.yml b/.github/workflows/branch-policy.yml index 31701eb6..1dca5b78 100644 --- a/.github/workflows/branch-policy.yml +++ b/.github/workflows/branch-policy.yml @@ -8,6 +8,8 @@ jobs: check-source-branch: name: Branch Policy Check runs-on: ubuntu-latest + # Observed over 31 runs: max 0.1 min. + timeout-minutes: 10 steps: - name: Verify source branch run: | diff --git a/.github/workflows/branch-protection.yml b/.github/workflows/branch-protection.yml index e2efcf6e..7ef08cea 100644 --- a/.github/workflows/branch-protection.yml +++ b/.github/workflows/branch-protection.yml @@ -2,10 +2,10 @@ name: Branch Protection on: pull_request: - branches: - - main - - beta + branches: [main, beta] + +permissions: {} jobs: - check: - uses: Conduction/.github/.github/workflows/branch-protection.yml@main + branch-protection: + uses: ConductionNL/.github/.github/workflows/branch-protection.yml@main diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 37a01099..edc81a64 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -7,12 +7,83 @@ on: branches: [main, beta, development] workflow_dispatch: +# Deduplicating a `push` run against the `pull_request` run for the SAME head +# ref is the point of this block, and for a feature branch it is exactly right: +# two runs of identical jobs, one of them wasted. +# +# It is wrong for `main` and `development`, because the push run there is NOT a +# duplicate — it is the only carrier of the push-only jobs: "Coverage Baseline +# Check" (`github.event_name == 'push'`), "SBOM" and "Features Extract". And +# those two branches always have an open PR whose `head_ref` IS the branch +# name: the standing "Release: merge development into beta" (#22 here). +# `github.head_ref` on that PR run and `github.ref_name` on the push run both +# render `development`, so both landed in the identical group +# `quality-development`, and `cancel-in-progress` killed whichever started +# first — always the push run, by a few seconds. +# +# Measured on this repo: 14 of the last 20 `development` push runs were +# cancelled within ~70s of starting — e.g. 31047886687 (41s), 31038167672 +# (68s), 31034263500 (54s), 31031059278 (42s). That duration is the +# discriminator: the shared workflow's `timeout-minutes: 45` cancellation lands +# at 45m16s–45m28s, so these are concurrency kills. This repo is the worst hit +# in the fleet. +# +# On the surviving PR run "Coverage Baseline Check" reports `skipped`, which is +# CORRECT for a pull_request event and renders exactly like a pass. So the gate +# appears on both runs and executes on neither — a dead gate of the +# permanently-pending shape. +# +# Suffixing only the default-branch push keeps feature-branch dedup untouched +# (`quality-feature/x` for both events, exactly as before) and gives the two +# default branches' push runs a lane of their own. +# +# Proven in openconnector#1158: its first-ever completed `development` push run +# (31048998594) executed Coverage Baseline Check, SBOM and Features Extract. +concurrency: + group: quality-${{ github.head_ref || github.ref_name }}${{ (github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'development')) && '-push' || '' }} + cancel-in-progress: true + +# Permission CEILING for the called quality pipeline. GitHub statically +# validates the called workflow's declared job permissions against this +# grant — even for jobs that are disabled — so it must cover the maximum +# any nested job declares: journeydoc-capture (contents+actions write), +# update-baseline / features-extract (contents write), and the Quality +# Report PR comment (issues / pull-requests write). +permissions: + contents: write + actions: write + issues: write + pull-requests: write + jobs: quality: - uses: Conduction/.github/.github/workflows/quality.yml@main + uses: ConductionNL/.github/.github/workflows/quality.yml@main with: app-name: launchpad php-version: "8.3" + # Pinned, because the shared workflow's DEFAULT is '["stable31", "stable32"]' + # and stable31 CANNOT WORK here. `additional-apps` below installs + # openregister, which declares min-version="32" (its + # lib/ContextChat/ContentProvider.php implements + # OCP\ContextChat\IContentProvider, absent from core before NC32). On NC31 + # `occ app:enable openregister` fails with + # App "Open Register" cannot be installed because it is not compatible + # with this version of the server. + # and the run continues anyway, because that failure is only a ::warning::. + # Every /apps/openregister/... call then returns Nextcloud's HTML 404 page. + # + # Order matters as much as membership: newman, playwright and + # journeydoc-capture all check out `fromJSON(nextcloud-test-refs)[0]` as + # their single server, so with the inherited default those three jobs ran + # entirely on the version openregister cannot load. + # + # THE LIST IS THE WHOLE DECLARED RANGE. This comment previously said + # "launchpad's own appinfo/info.xml floor stays at 29 … the NC32 constraint + # here is a property of the CI fixture, not of launchpad's code" — that is + # no longer true of the file it describes. info.xml on this branch declares + # , so 32 is the app's own + # floor, not a fixture artefact, and 32, 33 and 34 each get a leg. + nextcloud-test-refs: '["stable34", "stable32", "stable33"]' enable-psalm: true enable-phpstan: true enable-phpmetrics: true @@ -28,11 +99,145 @@ jobs: # postman fixture-id wiring repaired and assertion drift # resolved. 196 assertions / 0 failures locally. # - # Playwright remains disabled — the shared workflow's - # PHP-built-in-server lifetime is the blocker (cross-repo PR - # `Conduction/.github#37`). The `tests/e2e/global-setup.ts` - # `ensureBundleBuilt()` helper is in place so once the shared - # workflow lands, flipping the gate Just Works. + # - Playwright — ENABLED, against the root suite minus a measured + # exclusion list, NOT against the four-test `tests/e2e/ci/` subset + # it used to run. + # + # The subset was a deliberate "green floor that grows", and as a + # floor it worked. What it could not do is tell anyone the truth + # about coverage. gate-19 reads the ROOT `playwright.config.ts`; + # the workflow resolves `/playwright.config.ts` + # FIRST, so it read a different file — and nothing compared them. + # Measured 2026-08-10 (launchpad#82): **CI executed 4 tests while + # 113 existed**, and of **117 `@e2e` annotations only 9** were in + # files CI ran. gate-19 reported 71 scenarios covered; 4 had an + # executing test behind them. + # + # `tests/e2e` deliberately contains NO `playwright.config.ts`, so + # the workflow's fallback selects the root one — the same file + # gate-19 parses. The two cannot drift without an edit to that file. + # Its `testIgnore` names every excluded spec with the run that + # measured it (31367057618: 65 of 80 passed). enable-phpunit: true enable-newman: true newman-environment-path: "tests/integration/local.env.json" + # Creates the non-admin account the collection's authorization assertions + # need. Without it `{{regularUser}}` stays unresolved, the request arrives + # with junk credentials, and `POST /api/role-feature-permissions + # non-admin → 403` gets a 400 instead — failing while testing nothing about + # authorization. + # + # A SCRIPT, not an inline command: the shared workflow runs this through + # `eval ` UNQUOTED, so any shell metacharacter is parsed at the outer + # level. Measured on the Playwright equivalent, `( … ) && ( … )` is a syntax + # error and `sh -c '… ; …'` splits at the wrong level. `bash ` is one + # word and cannot be mis-parsed. Path is relative to `server/`. + newman-seed-command: bash apps/launchpad/tests/integration/seed.sh + # OpenRegister must be present for the integration suite to mean anything. + # The Newman collection asserts OpenRegister-backed behaviour — the + # AppHost observability engine behind /api/health and /api/metrics, and the + # dashboard objects behind the v2 manifest — so without it the suite was + # measuring a degraded instance. The job previously reported success only + # because `composer test:all` ended in `|| echo '…skipping'` and always + # exited 0; with that removed, 17 of 220 assertions surfaced as failures. + # + # `ref: development` is REQUIRED, not cosmetic. The default is `main`, and + # `lib/Service/Rbac/ObjectGrantResolver.php` — which + # ManifestController::fetchGrantedDashboards() resolves for the shared- + # dashboard source — exists only on `development`. Pinned to `main` the + # grant lookup would fail soft to owned-only and the suite would quietly + # test less than it appears to. + additional-apps: '[{"repo":"ConductionNL/openregister","app":"openregister","ref":"development"}]' + enable-playwright: true + # Names the real suite directory. It holds no config of its own, so the + # workflow falls back to the root `playwright.config.ts` — which is the + # file gate-19 reads. Same file, same testIgnore, no drift. + playwright-test-path: tests/e2e + # The grant spec needs a second, non-admin account to be the share + # recipient; a grant to yourself proves nothing. + # + # This was an inline `… user:add … || true`. The `|| true` was there for a + # real reason — the suite must survive a re-run against a warm instance — + # but it tolerated EVERYTHING, not just "already exists": a rejected + # password, a missing occ or a broken database all exited 0 and let + # Playwright start with no grantee. The script keeps the tolerance and + # narrows it to the postcondition that matters (the account exists + # afterwards), which it establishes by asking the instance. See + # tests/e2e/seed.test.sh, which asserts both arms — including the one + # that must fail. + playwright-seed-command: bash apps/launchpad/tests/e2e/seed.sh + + # ── Frontend Check legs ────────────────────────────────────────────── + # `frontend-checks` defaults to `[]`, and an empty list means the shared + # workflow emits NO "Frontend Check" job at all — so `check:manifest` ran + # nowhere while the run still looked complete. It is a self-contained + # `node scripts/check-manifest.js`, which is what a leg has to be (each + # leg is a fresh job with its own checkout + `npm ci`). + # Measured on this tree before enabling: PASSES. It is enabled to keep it + # passing, not because it is currently broken. + # `test` is NOT listed: "Frontend Tests (unit)" already runs it. + frontend-checks: '["check:manifest"]' + + # ── Coverage ratchet ───────────────────────────────────────────────── + # `enable-coverage-guard` defaults to FALSE, which is why both + # "Coverage Baseline Protection" and "Coverage Baseline Check" have only + # ever reported `skipped`. It needs two inputs this repo did not have, + # both added in this commit: `scripts/coverage-guard.php` (byte-identical + # to openregister's) and `.coverage-baseline` = 49.47, this repo's own + # measured coverage (11200 of 22642 statements) read from clover.xml in + # the `coverage-report` artifact of run 30911179742. + enable-coverage-guard: true + + # ── Hydra mechanical gates ─────────────────────────────────────────── + # `enable-hydra-gates` defaults to FALSE, so this tier has never executed + # here — the job reported `skipped`, which the Quality Report renders + # identically to a pass. Pinned to v1.0.1 so a change to the gate package + # cannot move this repo's verdict without a commit here. + # `enable-axe` deliberately NOT set: a vanilla Nextcloud 34 already carries + # serious/critical violations from core's own UI. + # + # v1.0.1 -> v1.3.0 (ConductionNL/.github#159). Two defects, one bump. + # + # 1. STALE. v1.0.1 is `f4d9756` (2026-08-03) and predates three gate + # fixes, so every Hydra Gates run this repo has ever made executed a + # script in which 16 gates reported PASS when their helper never ran + # (#147), gate-33 had no axe report to read and never said so (#148), + # and gates 6 and 7 reported PASS on an EMPTY scope (#149). A gate + # that reports PASS without running emits a tick identical to a real + # one, which is why nothing in this repo's history shows it. + # + # 2. RED. quality.yml is referenced `@main` while this package is + # PINNED, so the two can desync — and on 2026-08-05 they did. #164 + # flipped `hydra-gates-require-full-coverage` to default TRUE, but + # the accounting that makes that flag survivable (NOT APPLICABLE, as + # distinct from a structural or a wiring gap) ships in the PACKAGE. + # So every pin older than `f7eaf2a` now fails the coverage assertion + # for gates it has no subject matter for. Measured diff-scoped, + # exactly as CI scopes it: + # v1.0.1 exit 98 FAIL — "GATES THAT DID NOT RUN: 24 33" + # v1.3.0 exit 0 PASS — those gates named NOT APPLICABLE + # The old pin was not merely stale, it was failing this repo's CI for + # a reason that had nothing to do with this repo. + # + # 3. DEAD AGAIN, same mechanism, third time (2026-08-06). The pin was + # the defect, not its value. quality.yml floats `@main` and executes + # gate scripts BY PATH inside the pinned package, so every new gate + # added at @main is a path that v1.3.0 does not contain: + # + # hydra-gates-ref 'v1.3.0' does not contain: + # scripts/axe-run.cjs + # scripts/lib/check_spec_anchors.py + # scripts/lib/check_form_labels.py + # scripts/lib/check_license_triangle.py + # + # The job failed for a reason that, again, had nothing to do with + # this repo — CI itself says so: "This is NOT a code-quality finding + # about your repository." Bumping the pin to today's tag would only + # reset the same expiry clock a fourth time. + # + # So: the pin is REMOVED, not bumped. The shared workflow defaults this + # input to `main` and states "CALLERS SHOULD NOT SET THIS AT ALL"; the + # rest of the fleet sets nothing (scholiq notes the omission is + # deliberate). Floating both halves keeps caller and callee in step, + # which is the only configuration in which a pinned path cannot expire. + enable-hydra-gates: true diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 3608a532..18138278 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -8,6 +8,6 @@ on: jobs: deploy: - uses: Conduction/.github/.github/workflows/documentation.yml@main + uses: ConductionNL/.github/.github/workflows/documentation.yml@main with: cname: launchpad.conduction.nl diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index 969bc65b..a122c160 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -12,7 +12,7 @@ on: jobs: triage: - uses: Conduction/.github/.github/workflows/issue-triage.yml@feature/openspec-project-sync + uses: ConductionNL/.github/.github/workflows/issue-triage.yml@main with: app-name: launchpad backlog-existing: ${{ github.event_name == 'workflow_dispatch' && inputs.backlog-existing || false }} diff --git a/.github/workflows/openspec-sync.yml b/.github/workflows/openspec-sync.yml deleted file mode 100644 index d90bcb2b..00000000 --- a/.github/workflows/openspec-sync.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: OpenSpec Sync - -on: - push: - branches: [development] - paths: ['openspec/**'] - workflow_dispatch: - -jobs: - sync: - uses: Conduction/.github/.github/workflows/openspec-sync.yml@feature/openspec-project-sync - with: - app-name: launchpad - secrets: - PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }} diff --git a/.github/workflows/release-beta.yml b/.github/workflows/release-beta.yml index aeb2d684..0886f288 100644 --- a/.github/workflows/release-beta.yml +++ b/.github/workflows/release-beta.yml @@ -7,7 +7,7 @@ on: jobs: release: - uses: Conduction/.github/.github/workflows/release-beta.yml@main + uses: ConductionNL/.github/.github/workflows/release-beta.yml@main with: app-name: launchpad secrets: inherit diff --git a/.github/workflows/release-stable.yml b/.github/workflows/release-stable.yml index 2236b496..c7b8fa01 100644 --- a/.github/workflows/release-stable.yml +++ b/.github/workflows/release-stable.yml @@ -7,7 +7,7 @@ on: jobs: release: - uses: Conduction/.github/.github/workflows/release-stable.yml@main + uses: ConductionNL/.github/.github/workflows/release-stable.yml@main with: app-name: launchpad secrets: inherit diff --git a/.github/workflows/sync-to-beta.yml b/.github/workflows/sync-to-beta.yml index a8c33439..76a44269 100644 --- a/.github/workflows/sync-to-beta.yml +++ b/.github/workflows/sync-to-beta.yml @@ -7,4 +7,4 @@ on: jobs: sync: - uses: Conduction/.github/.github/workflows/sync-to-beta.yml@main + uses: ConductionNL/.github/.github/workflows/sync-to-beta.yml@main diff --git a/.gitignore b/.gitignore index da994211..33e4d687 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,7 @@ bom-npm.cdx.json /docs/sendent-analysis.md /sendent-workspace-main/ /2026.*_sendent-workspace-main.zip + +# Local MCP server configuration — carries API keys, must never be committed. +# A live n8n API key reached the tip tree of 37 local branches before this was added. +.mcp.json diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 00000000..db584532 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,20 @@ +getFinder() + ->notPath('vendor') + ->notPath('node_modules') + ->notPath('build') + ->in(__DIR__ . '/lib') + ->in(__DIR__ . '/tests'); + +return $config; diff --git a/.phpunit.result.cache b/.phpunit.result.cache deleted file mode 100644 index 1bbcd6f3..00000000 --- a/.phpunit.result.cache +++ /dev/null @@ -1 +0,0 @@ -{"version":2,"defects":{"OCA\\LaunchPad\\Tests\\Unit\\Service\\AdminSettingsServiceTest::testGetSettingsReturnsAllExpectedKeys":8,"OCA\\LaunchPad\\Tests\\Unit\\Service\\AdminSettingsServiceTest::testGetSettingsUsesDefaultsWhenEmpty":8,"OCA\\LaunchPad\\Tests\\Unit\\Service\\AdminSettingsServiceTest::testGetSettingsUsesStoredValues":8},"times":{"OCA\\LaunchPad\\Tests\\Unit\\Service\\AdminSettingsServiceTest::testGetSettingsReturnsAllExpectedKeys":0.02,"OCA\\LaunchPad\\Tests\\Unit\\Service\\AdminSettingsServiceTest::testGetSettingsUsesDefaultsWhenEmpty":0.002,"OCA\\LaunchPad\\Tests\\Unit\\Service\\AdminSettingsServiceTest::testGetSettingsUsesStoredValues":0.001}} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..3e19f6a1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,7 @@ +# Launchpad + +This is a standalone repository that happens to live under +`nextcloud-docker-dev/workspace/server/apps-extra/`. The instructions in the +parent `server/` directory (its `CLAUDE.md` / `AGENTS.md`) describe the +Nextcloud server core and its bundled apps — they do **not** govern this repo. +Treat launchpad's own conventions as authoritative here. diff --git a/README.md b/README.md index 63bf1629..d4911e6e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@

Latest release - License + License Code quality Documentation

diff --git a/appinfo/info.xml b/appinfo/info.xml index f48e0f03..ccb1e9a5 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -1,7 +1,7 @@ @@ -23,6 +23,9 @@ - **Widget styling** — Customize colors, borders, and titles for each individual widget - **Compulsory widgets** — Admins can pin important widgets that users cannot remove - **Full Nextcloud compatibility** — Works with every existing Nextcloud dashboard widget out of the box +- **Role-based widget access** — Restrict which widget types a group of users may add, resolved from Nextcloud group membership +- **Dashboard sharing** — Share a dashboard with specific users or groups, or publish a brute-force-protected read-only public link +- **Group dashboards** — One shared dashboard per group, in addition to personal dashboards Perfect for organizations that want consistent, curated dashboards for their teams while still giving users freedom to personalize. @@ -41,69 +44,95 @@ Free and open source under the EUPL-1.2 license. - **Widget-styling** — Pas kleuren, randen en titels aan voor elke individuele widget - **Verplichte widgets** — Beheerders kunnen belangrijke widgets vastzetten die gebruikers niet kunnen verwijderen - **Volledige Nextcloud-compatibiliteit** — Werkt direct met elke bestaande Nextcloud dashboard-widget +- **Rolgebaseerde widget-toegang** — Beperk welke widget-types een gebruikersgroep mag toevoegen, op basis van Nextcloud-groepslidmaatschap +- **Dashboards delen** — Deel een dashboard met specifieke gebruikers of groepen, of publiceer een tegen brute-force beveiligde alleen-lezen publieke link +- **Groepsdashboards** — Eén gedeeld dashboard per groep, naast persoonlijke dashboards Ideaal voor organisaties die consistente, samengestelde dashboards willen voor hun teams, terwijl gebruikers de vrijheid houden om te personaliseren. Vrij en open source onder de EUPL-1.2-licentie. ]]> - 1.0.5-unstable.11 - agpl + 1.0.5-unstable.15 + EUPL-1.2 Conduction LaunchPad - https://github.com/ConductionNL/launchpad - https://github.com/ConductionNL/launchpad - https://github.com/ConductionNL/launchpad + https://codeberg.org/Conduction/launchpad + https://codeberg.org/Conduction/launchpad + https://codeberg.org/Conduction/launchpad customization organization dashboard - https://github.com/ConductionNL/launchpad - https://github.com/ConductionNL/launchpad/discussions - https://github.com/ConductionNL/launchpad/issues - https://github.com/ConductionNL/launchpad - - https://raw.githubusercontent.com/ConductionNL/launchpad/main/img/app-store.svg - https://raw.githubusercontent.com/ConductionNL/launchpad/main/img/screenshot.png - + https://codeberg.org/Conduction/launchpad + https://codeberg.org/Conduction/launchpad/issues + https://codeberg.org/Conduction/launchpad + + https://codeberg.org/Conduction/launchpad/raw/branch/main/img/screenshot-dashboard.png + https://codeberg.org/Conduction/launchpad/raw/branch/main/img/screenshot-widgets.png + https://codeberg.org/Conduction/launchpad/raw/branch/main/img/screenshot-admin.png + + - - openregister + - - - - + OCA\LaunchPad\BackgroundJob\OrphanedDataCleanupJob + + OCA\LaunchPad\BackgroundJob\HealthPingRefreshJob - - OCA\LaunchPad\Repair\InitializeActions - - OCA\LaunchPad\Repair\SeedRolePermissions - - OCA\LaunchPad\Repair\RegisterBackgroundJobs - OCA\LaunchPad\Repair\InitializeActions + + OCA\LaunchPad\Repair\ApplyActionBaseline OCA\LaunchPad\Repair\PurgeOrphanedCascadeData OCA\LaunchPad\Repair\RegisterBackgroundJobs + + OCA\LaunchPad\Repair\ImportLaunchpadRegister + + OCA\LaunchPad\Repair\InitializeActions + + OCA\LaunchPad\Repair\ApplyActionBaseline + + OCA\LaunchPad\Repair\SeedRolePermissions + + OCA\LaunchPad\Repair\RegisterBackgroundJobs + + OCA\LaunchPad\Repair\ImportLaunchpadRegister + - - OCA\LaunchPad\Settings\LaunchPadAdmin - OCA\LaunchPad\Settings\LaunchPadAdminSection - - OCA\LaunchPad\Command\ExportCommand @@ -126,12 +155,25 @@ Vrij en open source onder de EUPL-1.2-licentie. OCA\LaunchPad\Command\DemoShowcasesListCommand OCA\LaunchPad\Command\SetupCommand - - OCA\LaunchPad\Command\MigrateStorageToGroupFolder - - OCA\LaunchPad\Command\ToggleStorageSetting + + OCA\LaunchPad\Settings\LaunchPadAdmin + OCA\LaunchPad\Settings\LaunchPadAdminSection + + + + + + OCA\LaunchPad\Activity\Extension + + + LaunchPad @@ -140,11 +182,4 @@ Vrij en open source onder de EUPL-1.2-licentie. -5 - - - - OCA\LaunchPad\Activity\Extension - diff --git a/appinfo/routes.php b/appinfo/routes.php index 7d8f9c2c..75f26c7d 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -3,8 +3,8 @@ declare(strict_types=1); /** - * SPDX-FileCopyrightText: 2024 LaunchPad Contributors - * SPDX-License-Identifier: AGPL-3.0-or-later + * SPDX-FileCopyrightText: 2024 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 */ return [ @@ -86,6 +86,21 @@ // dashboard does not exist. ['name' => 'dashboardApi#viewEvent', 'url' => '/api/dashboards/{uuid}/view-event', 'verb' => 'POST', 'requirements' => ['uuid' => '[A-Za-z0-9\-]+']], + + // REQ-TANLT-002: record a tile click. Authed users only; the + // controller short-circuits silently when the user has opted out + // or analytics is globally disabled (same reused REQ-ANLT-003/004/005 + // gates as the dashboard view-event route above). Returns HTTP 204 + // on success, 404 when the placement does not exist. `placementId` + // is constrained to digits so the router never confuses it with a + // literal segment. + ['name' => 'tileAnalytics#recordClick', 'url' => '/api/tile-click/{placementId}', 'verb' => 'POST', + 'requirements' => ['placementId' => '\d+']], + // REQ-TANLT-003: lets the frontend hook know whether tracking is + // currently active for the calling user, so it can suppress the + // record call without re-implementing the gate logic client-side. + ['name' => 'tileAnalytics#config', 'url' => '/api/tile-analytics/config', 'verb' => 'GET'], + // REQ-DASH-026: nested dashboard tree. ['name' => 'dashboardApi#tree', 'url' => '/api/dashboards/tree', 'verb' => 'GET'], // REQ-DASH-027: slug-chain path resolution. The {path} placeholder @@ -150,12 +165,18 @@ 'url' => '/api/dashboards/{uuid}/public-shares/{id}', 'verb' => 'DELETE', 'requirements' => ['uuid' => '[A-Za-z0-9\-]+', 'id' => '\d+']], // Public (anonymous) share render and unlock (REQ-PSHR-004, REQ-PSHR-005). - // Both are #[PublicPage] + #[NoCSRFRequired] on the controller methods. - // Registered BEFORE the deep-link catch-all at the bottom. - ['name' => 'publicShare#show', 'url' => '/s/{token}', 'verb' => 'GET', + // All #[PublicPage] + #[NoCSRFRequired] on the controller methods. + // Registered BEFORE the deep-link catch-all at the bottom. `/s/{token}` + // serves the anonymous read-only HTML page (page#publicShare); the SPA it + // boots fetches its data from `/s/{token}/data` (publicShare#show). The + // more-specific /data + /unlock segments are declared before the bare + // token page route so they win in matching. + ['name' => 'publicShare#show', 'url' => '/s/{token}/data', 'verb' => 'GET', 'requirements' => ['token' => '[A-Za-z0-9]+']], ['name' => 'publicShare#unlock', 'url' => '/s/{token}/unlock', 'verb' => 'POST', 'requirements' => ['token' => '[A-Za-z0-9]+']], + ['name' => 'page#publicShare', 'url' => '/s/{token}', 'verb' => 'GET', + 'requirements' => ['token' => '[A-Za-z0-9]+']], // Kiosk playlist management endpoints (REQ-KIOSK-002). Owner-or-admin, // `#[NoAdminRequired]` + service-layer per-dashboard guards. The literal @@ -204,6 +225,21 @@ 'url' => '/api/dashboards/{uuid}/reactions', 'verb' => 'POST', 'requirements' => ['uuid' => '[A-Za-z0-9\-]+']], + // Mandatory-read acknowledgement endpoints (REQ-ACK-002..006). + // The `/report/{announcementKey}/csv` route is registered BEFORE the + // plain report route so the `/csv` suffix is matched first, and both + // come before the literal `/pending` and root POST routes. + ['name' => 'acknowledgement#reportCsv', + 'url' => '/api/acknowledgements/report/{announcementKey}/csv', 'verb' => 'GET', + 'requirements' => ['announcementKey' => '[A-Za-z0-9\-]+']], + ['name' => 'acknowledgement#report', + 'url' => '/api/acknowledgements/report/{announcementKey}', 'verb' => 'GET', + 'requirements' => ['announcementKey' => '[A-Za-z0-9\-]+']], + ['name' => 'acknowledgement#pending', + 'url' => '/api/acknowledgements/pending', 'verb' => 'GET'], + ['name' => 'acknowledgement#acknowledge', + 'url' => '/api/acknowledgements', 'verb' => 'POST'], + // Dashboard versioning endpoints (REQ-VERS-001..009). // `{uuid}` is the dashboard UUID; `{versionNumber}` is the integer // version number. Routes are registered BEFORE the personal @@ -257,6 +293,11 @@ ['name' => 'ruleApi#updateRule', 'url' => '/api/rules/{ruleId}', 'verb' => 'PUT'], ['name' => 'ruleApi#deleteRule', 'url' => '/api/rules/{ruleId}', 'verb' => 'DELETE'], + // conditional-visibility-editor: read-only, non-persisting + // "preview as audience/date" — #[NoAdminRequired] on + // VisibilityPreviewController::preview(). + ['name' => 'visibilityPreview#preview', 'url' => '/api/visibility/preview', 'verb' => 'POST'], + // Role-feature permissions (REQ-RFP-001..010). Admin-only — the // controller calls `requireAdmin()` on every method. Sits with // the rest of the admin-scoped routes; the duplicate @@ -298,6 +339,11 @@ // streamer is intentionally NOT under `/api/...` because it // returns binary bytes, not a JSON envelope. ['name' => 'resource#upload', 'url' => '/api/resources', 'verb' => 'POST'], + // Raw multipart upload — REQ-RES-014. Admin-only (same security as the + // base64 endpoint above); accepts a single `file` multipart field with + // no base64 so large images/GIFs never become a huge in-browser string. + // Registered before the wildcard `/resource/{filename}` streamer. + ['name' => 'resource#uploadMultipart', 'url' => '/api/resources/upload', 'verb' => 'POST'], // Resource listing — REQ-RES-007. Logged-in user only (no admin // gate); the listed names are already referenced from rendered // dashboards so admin gating would lock dashboards out of their @@ -326,6 +372,10 @@ // `{uuid}/preview-image` suffix matches first. ['name' => 'admin#uploadTemplatePreviewImage', 'url' => '/api/admin/templates/{uuid}/preview-image', 'verb' => 'POST', 'requirements' => ['uuid' => '[A-Za-z0-9\-]+']], + // Admin template re-sync (REQ-RESYNC-001). Registered BEFORE the + // `/api/admin/templates/{id}` wildcard routes so the literal + // `{id}/resync` suffix matches first, same as preview-image above. + ['name' => 'admin#resyncTemplate', 'url' => '/api/admin/templates/{id}/resync', 'verb' => 'POST'], ['name' => 'admin#getTemplate', 'url' => '/api/admin/templates/{id}', 'verb' => 'GET'], ['name' => 'admin#updateTemplate', 'url' => '/api/admin/templates/{id}', 'verb' => 'PUT'], ['name' => 'admin#deleteTemplate', 'url' => '/api/admin/templates/{id}', 'verb' => 'DELETE'], @@ -427,6 +477,18 @@ ['name' => 'analytics#dashboardDetail', 'url' => '/api/admin/analytics/dashboards/{uuid}', 'verb' => 'GET', 'requirements' => ['uuid' => '[A-Za-z0-9\-]+']], + // Tile usage-analytics admin endpoints (REQ-TANLT-004..005) — a + // strict downward extension of the dashboard view-analytics admin + // endpoints above. All admin-only via ADR-023 action authorization + // inside the controller. The literal `top` and `export` segments + // and the `by-dashboard` prefix precede any wildcard so the router + // never confuses them. + ['name' => 'tileAnalytics#topTiles', 'url' => '/api/admin/analytics/tiles/top', 'verb' => 'GET'], + ['name' => 'tileAnalytics#exportCsv', 'url' => '/api/admin/analytics/tiles/export', 'verb' => 'GET'], + ['name' => 'tileAnalytics#dashboardBreakdown', + 'url' => '/api/admin/analytics/tiles/by-dashboard/{uuid}', 'verb' => 'GET', + 'requirements' => ['uuid' => '[A-Za-z0-9\-]+']], + // Background feed-refresh trigger (REQ-FRJ-010). Admin-only via // runtime `IGroupManager::isAdmin` check inside the controller. ['name' => 'admin#refreshFeedsNow', 'url' => '/api/admin/feeds/refresh-now', 'verb' => 'POST'], @@ -479,6 +541,41 @@ 'url' => '/api/admin/demo-showcases/{id}', 'verb' => 'DELETE', 'requirements' => ['id' => '[a-z0-9\-]+']], + // Weather widget — cached reading for one placement (REQ-WEATHER-001). + // View-time ACL guarded in the controller; never returns the provider + // API key or raw provider URL. + ['name' => 'weather#show', 'url' => '/api/weather/{placementId}', 'verb' => 'GET', + 'requirements' => ['placementId' => '\d+']], + + // Live-data tile widget — cached, resolved value for one placement + // (REQ-LIVETILE-003). View-time ACL guarded in the controller; never + // returns the source URL, headers, or credentials. The two + // multi-segment routes below are registered BEFORE the single-segment + // `{placementId}` route so a literal `connector/status` / + // `validate-source` path is never mistaken for a numeric placement id. + ['name' => 'liveTile#connectorStatus', 'url' => '/api/livetile/connector/status', 'verb' => 'GET'], + ['name' => 'liveTile#validateSource', 'url' => '/api/livetile/validate-source', 'verb' => 'POST'], + ['name' => 'liveTile#show', 'url' => '/api/livetile/{placementId}', 'verb' => 'GET', + 'requirements' => ['placementId' => '\d+']], + + // Iframe-embed widget — save-time allow-list validation + // (REQ-IFRAME-002). No per-placement data endpoint: the browser + // embeds the target URL directly, config lives in `widgetContent`. + ['name' => 'iframe#validateUrl', 'url' => '/api/iframe/validate-url', 'verb' => 'POST'], + // Server-side framing-refusal check (REQ-IFRAME-003) — the browser + // cannot detect an X-Frame-Options / frame-ancestors block, so the + // widget asks the server before rendering the iframe. + ['name' => 'iframe#checkFramable', 'url' => '/api/iframe/framable', 'verb' => 'POST'], + + // Service health ping — cached online/offline/degraded badge for one + // placement (REQ-HPING-003). View-time ACL guarded in the controller; + // never returns the health URL, headers, or upstream response body. + // The literal `validate` route is registered BEFORE the single-segment + // `{placementId}` route so it is never mistaken for a numeric placement id. + ['name' => 'healthPing#validate', 'url' => '/api/health-ping/validate', 'verb' => 'POST'], + ['name' => 'healthPing#show', 'url' => '/api/health-ping/{placementId}', 'verb' => 'GET', + 'requirements' => ['placementId' => '\d+']], + // Resolve a dashboard's canonical slug-chain path (used by the // frontend for outbound URL sync after a sidebar switch). // Registered BEFORE the catch-all deep-link route so the literal diff --git a/composer.json b/composer.json index 82cd9233..ad9f87e3 100644 --- a/composer.json +++ b/composer.json @@ -1,101 +1,104 @@ { - "name": "conductionnl/launchpad", - "description": "Enhanced dashboard with grid layout and admin controls for Nextcloud", - "type": "project", - "license": "EUPL-1.2", - "authors": [ - { - "name": "LaunchPad Contributors" - } - ], - "require": { - "php": "^8.3" - }, - "require-dev": { - "cyclonedx/cyclonedx-php-composer": "^6.2", - "edgedesign/phpqa": "^1.27", - "nextcloud/coding-standard": "^1.4", - "nextcloud/ocp": "^31.0", - "phpcsstandards/phpcsextra": "^1.4", - "phpmd/phpmd": "^2.15", - "phpmetrics/phpmetrics": "^2.8", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^10", - "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.9", - "twig/twig": "^3.27.0", - "vimeo/psalm": "^5.26" - }, - "autoload": { - "psr-4": { - "OCA\\LaunchPad\\": "lib/" - } - }, - "autoload-dev": { - "psr-4": { - "OCP\\": "vendor/nextcloud/ocp/OCP/", - "NCU\\": "vendor/nextcloud/ocp/NCU/", - "Unit\\": "tests/Unit/" - } - }, - "scripts": { - "lint": "find . -name \\*.php -not -path './vendor/*' -not -path './build/*' -print0 | xargs -0 -n1 php -l", - "lint:initial-state": "php scripts/lint-initial-state.php", - "lint:spec-annotations": "php tools/check-spec-annotations.php", - "cs:check": "./vendor/bin/phpcs --standard=phpcs.xml", - "cs:fix": "./vendor/bin/phpcbf --standard=phpcs.xml", - "phpcs": "./vendor/bin/phpcs --standard=phpcs.xml", - "phpcs:fix": "./vendor/bin/phpcbf --standard=phpcs.xml", - "phpcs:output": "./vendor/bin/phpcs --standard=phpcs.xml --report=json lib/ 2>/dev/null | tail -1 > phpcs-output.json", - "phpmd": "vendor/bin/phpmd lib text phpmd.xml", - "phpmetrics": "./vendor/bin/phpmetrics --report-html=phpmetrics lib/", - "phpmetrics:violations": "./vendor/bin/phpmetrics --violations-xml=phpmetrics/violations.xml lib/", - "psalm": "./vendor/bin/psalm --threads=1 --no-cache || echo 'Psalm not installed, skipping...'", - "phpstan": "./vendor/bin/phpstan analyse --memory-limit=1G", - "test": "phpunit --configuration phpunit.xml", - "test:unit": "./vendor/bin/phpunit --configuration phpunit.xml --colors=always || echo 'Tests require Nextcloud environment, skipping...'", - "test:all": "./vendor/bin/phpunit --configuration phpunit.xml --colors=always || echo 'Tests require Nextcloud environment, skipping...'", - "test:integration": "if command -v newman >/dev/null 2>&1; then newman run tests/integration/launchpad.postman_collection.json --environment tests/integration/local.env.json; else npx --yes newman run tests/integration/launchpad.postman_collection.json --environment tests/integration/local.env.json; fi", - "newman": "@test:integration", - "newman:coverage": "node tests/integration/.coverage-check.js", - "check": "E=0; for CMD in lint phpcs psalm test:unit; do echo; echo \"=== $CMD ===\"; composer $CMD || E=1; done; echo; if [ $E -eq 0 ]; then echo \"ALL CHECKS PASSED\"; else echo \"SOME CHECKS FAILED (see above)\"; fi; exit $E", - "check:full": "E=0; for CMD in lint phpcs psalm phpstan test:all; do echo; echo \"=== $CMD ===\"; composer $CMD || E=1; done; echo; if [ $E -eq 0 ]; then echo \"ALL CHECKS PASSED\"; else echo \"SOME CHECKS FAILED (see above)\"; fi; exit $E", - "check:strict": "E=0; for CMD in lint lint:initial-state lint:spec-annotations phpcs phpmd psalm phpstan test:all; do echo; echo \"=== $CMD ===\"; composer $CMD || E=1; done; echo; if [ $E -eq 0 ]; then echo \"ALL CHECKS PASSED\"; else echo \"SOME CHECKS FAILED (see above)\"; fi; exit $E", - "fix": [ - "@cs:fix" - ], - "phpqa": "./vendor/bin/phpqa --report --analyzedDirs lib --buildDir phpqa", - "phpqa:full": "./vendor/bin/phpqa --report --analyzedDirs lib --buildDir phpqa --tools phpcs:0,phpmd:0,phploc:0,phpmetrics,phpcpd:0,parallel-lint:0", - "phpqa:ci": "./vendor/bin/phpqa --report --analyzedDirs lib --buildDir phpqa --tools phpcs,phpmd,phploc,phpmetrics,phpcpd,parallel-lint", - "qa:check": [ - "@phpqa" - ], - "qa:full": [ - "@phpqa:full" - ], - "test:coverage": "./vendor/bin/phpunit --configuration phpunit.xml --coverage-html=coverage/html --coverage-clover=coverage/clover.xml --colors=always", - "coverage:check": "php -r \"\\$xml = simplexml_load_file('coverage/clover.xml'); \\$metrics = \\$xml->project->metrics; \\$statements = (int)\\$metrics['statements']; \\$covered = (int)\\$metrics['coveredstatements']; \\$percentage = \\$statements > 0 ? round((\\$covered / \\$statements) * 100, 2) : 0; echo 'Coverage: ' . \\$percentage . '%' . PHP_EOL; exit(\\$percentage < 75 ? 1 : 0);\"", - "quality:phpcs-score": "./vendor/bin/phpcs --standard=phpcs.xml --report=json lib/ | php -r \"\\$json = json_decode(file_get_contents('php://stdin'), true); \\$errors = \\$json['totals']['errors'] ?? 0; \\$warnings = \\$json['totals']['warnings'] ?? 0; \\$score = 1000 - \\$errors - (\\$warnings / 2); echo 'PHPCS Score: ' . \\$score . ' (Errors: ' . \\$errors . ', Warnings: ' . \\$warnings . ')' . PHP_EOL;\"", - "quality:phpmd-score": "phpmd lib/ json phpmd.xml | php -r \"\\$input = file_get_contents('php://stdin'); \\$json = json_decode(\\$input, true); \\$violations = count(\\$json['files'] ?? []); \\$score = 1000 - (\\$violations * 10); echo 'PHPMD Score: ' . \\$score . ' (Violations: ' . \\$violations . ')' . PHP_EOL;\" || echo 'PHPMD not available'", - "quality:psalm-score": "./vendor/bin/psalm --output-format=json --no-cache | php -r \"\\$input = file_get_contents('php://stdin'); \\$json = json_decode(\\$input, true); \\$errors = count(\\$json ?? []); \\$score = 1000 - (\\$errors * 5); echo 'Psalm Score: ' . \\$score . ' (Errors: ' . \\$errors . ')' . PHP_EOL;\" || echo 'Psalm not available'", - "quality:phpstan-score": "./vendor/bin/phpstan analyse --memory-limit=1G --error-format=json --no-progress | php -r \"\\$input = file_get_contents('php://stdin'); \\$json = json_decode(\\$input, true); \\$errors = \\$json['totals']['file_errors'] ?? 0; \\$score = 1000 - (\\$errors * 5); echo 'PHPStan Score: ' . \\$score . ' (Errors: ' . \\$errors . ')' . PHP_EOL;\" || echo 'PHPStan not available'", - "quality:score": [ - "@quality:phpcs-score", - "@quality:phpmd-score", - "@quality:psalm-score", - "@quality:phpstan-score" - ] - }, - "config": { - "allow-plugins": { - "composer/package-versions-deprecated": true, - "dealerdirect/phpcodesniffer-composer-installer": true, - "cyclonedx/cyclonedx-php-composer": true - }, - "optimize-autoloader": true, - "sort-packages": true, - "platform": { - "php": "8.3" - } - } + "name": "conductionnl/launchpad", + "description": "Enhanced dashboard with grid layout and admin controls for Nextcloud", + "type": "project", + "license": "EUPL-1.2", + "authors": [ + { + "name": "LaunchPad Contributors" + } + ], + "require": { + "php": "^8.3" + }, + "require-dev": { + "conduction/coding-standard": "^1.0", + "conduction/hydra-gates": "^1.0", + "cyclonedx/cyclonedx-php-composer": "^6.2", + "edgedesign/phpqa": "^1.27", + "nextcloud/ocp": "^34.0", + "phpcsstandards/phpcsextra": "^1.4", + "phpmd/phpmd": "^2.15", + "phpmetrics/phpmetrics": "^2.8", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.9", + "twig/twig": "^3.27.0", + "vimeo/psalm": "^5.26" + }, + "autoload": { + "psr-4": { + "OCA\\LaunchPad\\": "lib/" + } + }, + "autoload-dev": { + "psr-4": { + "Unit\\": "tests/Unit/" + } + }, + "scripts": { + "lint": "find . -name \\*.php -not -path './vendor/*' -not -path './build/*' -print0 | xargs -0 -n1 php -l", + "lint:initial-state": "php scripts/lint-initial-state.php", + "lint:spec-annotations": "php tools/check-spec-annotations.php", + "lint:licenses": "bash scripts/check-license-headers.sh", + "cs:check": "php-cs-fixer fix --dry-run --diff", + "cs:fix": "php-cs-fixer fix", + "phpcs": "./vendor/bin/phpcs --standard=phpcs.xml", + "phpcs:fix": "./vendor/bin/phpcbf --standard=phpcs.xml", + "phpcs:output": "./vendor/bin/phpcs --standard=phpcs.xml --report=json lib/ 2>/dev/null | tail -1 > phpcs-output.json", + "phpmd": "E=0; ./vendor/bin/phpmd lib text phpmd.xml || E=$?; ./vendor/bin/phpmd lib text vendor/conduction/hydra-gates/quality-config/phpmd-unusedparams.xml --baseline-file phpmd.baseline.xml || E=$?; exit $E", + "phpmetrics": "./vendor/bin/phpmetrics --report-html=phpmetrics lib/", + "phpmetrics:violations": "./vendor/bin/phpmetrics --violations-xml=phpmetrics/violations.xml lib/", + "psalm": "./vendor/bin/psalm --threads=1 --no-cache", + "phpstan": "./vendor/bin/phpstan analyse --memory-limit=1G", + "test": "phpunit --configuration phpunit.xml", + "test:unit": "./vendor/bin/phpunit --configuration phpunit.xml --colors=always", + "test:all": "./vendor/bin/phpunit --configuration phpunit.xml --colors=always", + "test:integration": "if command -v newman >/dev/null 2>&1; then newman run tests/integration/launchpad.postman_collection.json --environment tests/integration/local.env.json; else npx --yes newman run tests/integration/launchpad.postman_collection.json --environment tests/integration/local.env.json; fi", + "newman": "@test:integration", + "newman:coverage": "node tests/integration/.coverage-check.js", + "check": "E=0; for CMD in lint phpcs psalm test:unit; do echo; echo \"=== $CMD ===\"; composer $CMD || E=1; done; echo; if [ $E -eq 0 ]; then echo \"ALL CHECKS PASSED\"; else echo \"SOME CHECKS FAILED (see above)\"; fi; exit $E", + "check:full": "E=0; for CMD in lint phpcs psalm phpstan test:all; do echo; echo \"=== $CMD ===\"; composer $CMD || E=1; done; echo; if [ $E -eq 0 ]; then echo \"ALL CHECKS PASSED\"; else echo \"SOME CHECKS FAILED (see above)\"; fi; exit $E", + "check:strict": "E=0; for CMD in lint lint:initial-state lint:spec-annotations lint:licenses phpcs phpmd psalm phpstan test:all; do echo; echo \"=== $CMD ===\"; composer $CMD || E=1; done; echo; if [ $E -eq 0 ]; then echo \"ALL CHECKS PASSED\"; else echo \"SOME CHECKS FAILED (see above)\"; fi; exit $E", + "fix": [ + "@cs:fix" + ], + "phpqa": "./vendor/bin/phpqa --report --analyzedDirs lib --buildDir phpqa", + "phpqa:full": "./vendor/bin/phpqa --report --analyzedDirs lib --buildDir phpqa --tools phpcs:0,phpmd:0,phploc:0,phpmetrics,phpcpd:0,parallel-lint:0", + "phpqa:ci": "./vendor/bin/phpqa --report --analyzedDirs lib --buildDir phpqa --tools phpcs,phpmd,phploc,phpmetrics,phpcpd,parallel-lint", + "qa:check": [ + "@phpqa" + ], + "qa:full": [ + "@phpqa:full" + ], + "test:coverage": "./vendor/bin/phpunit --configuration phpunit.xml --coverage-html=coverage/html --coverage-clover=coverage/clover.xml --colors=always", + "coverage:check": "php -r \"\\$xml = simplexml_load_file('coverage/clover.xml'); \\$metrics = \\$xml->project->metrics; \\$statements = (int)\\$metrics['statements']; \\$covered = (int)\\$metrics['coveredstatements']; \\$percentage = \\$statements > 0 ? round((\\$covered / \\$statements) * 100, 2) : 0; echo 'Coverage: ' . \\$percentage . '%' . PHP_EOL; exit(\\$percentage < 75 ? 1 : 0);\"", + "quality:phpcs-score": "./vendor/bin/phpcs --standard=phpcs.xml --report=json lib/ | php -r \"\\$json = json_decode(file_get_contents('php://stdin'), true); \\$errors = \\$json['totals']['errors'] ?? 0; \\$warnings = \\$json['totals']['warnings'] ?? 0; \\$score = 1000 - \\$errors - (\\$warnings / 2); echo 'PHPCS Score: ' . \\$score . ' (Errors: ' . \\$errors . ', Warnings: ' . \\$warnings . ')' . PHP_EOL;\"", + "quality:phpmd-score": "phpmd lib/ json phpmd.xml | php -r \"\\$input = file_get_contents('php://stdin'); \\$json = json_decode(\\$input, true); \\$violations = count(\\$json['files'] ?? []); \\$score = 1000 - (\\$violations * 10); echo 'PHPMD Score: ' . \\$score . ' (Violations: ' . \\$violations . ')' . PHP_EOL;\" || echo 'PHPMD not available'", + "quality:psalm-score": "./vendor/bin/psalm --output-format=json --no-cache | php -r \"\\$input = file_get_contents('php://stdin'); \\$json = json_decode(\\$input, true); \\$errors = count(\\$json ?? []); \\$score = 1000 - (\\$errors * 5); echo 'Psalm Score: ' . \\$score . ' (Errors: ' . \\$errors . ')' . PHP_EOL;\" || echo 'Psalm not available'", + "quality:phpstan-score": "./vendor/bin/phpstan analyse --memory-limit=1G --error-format=json --no-progress | php -r \"\\$input = file_get_contents('php://stdin'); \\$json = json_decode(\\$input, true); \\$errors = \\$json['totals']['file_errors'] ?? 0; \\$score = 1000 - (\\$errors * 5); echo 'PHPStan Score: ' . \\$score . ' (Errors: ' . \\$errors . ')' . PHP_EOL;\" || echo 'PHPStan not available'", + "quality:score": [ + "@quality:phpcs-score", + "@quality:phpmd-score", + "@quality:psalm-score", + "@quality:phpstan-score" + ], + "post-install-cmd": [ + "git config core.hooksPath .githooks || true" + ] + }, + "config": { + "allow-plugins": { + "composer/package-versions-deprecated": true, + "dealerdirect/phpcodesniffer-composer-installer": true, + "cyclonedx/cyclonedx-php-composer": true + }, + "optimize-autoloader": true, + "sort-packages": true, + "platform": { + "php": "8.3" + } + } } diff --git a/composer.lock b/composer.lock index a7676d2c..02eb061c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b298f765de93d95487463f84a88603b1", + "content-hash": "edc4c14f2d591cac0bb3cc2013991e7c", "packages": [], "packages-dev": [ { @@ -460,6 +460,110 @@ ], "time": "2024-05-06T16:37:16+00:00" }, + { + "name": "conduction/coding-standard", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/ConductionNL/coding-standard.git", + "reference": "a1854f13cb735e46ecd010767593b4d7bc90d974" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ConductionNL/coding-standard/zipball/a1854f13cb735e46ecd010767593b4d7bc90d974", + "reference": "a1854f13cb735e46ecd010767593b4d7bc90d974", + "shasum": "" + }, + "require": { + "nextcloud/coding-standard": "^1.4", + "php": "^8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Conduction\\CodingStandard\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "EUPL-1.2" + ], + "authors": [ + { + "name": "Conduction", + "homepage": "https://conduction.nl" + } + ], + "description": "Conduction coding standards for the PHP CS Fixer. Extends nextcloud/coding-standard — adds rules, never overrides them.", + "homepage": "https://github.com/ConductionNL/coding-standard", + "keywords": [ + "coding-standard", + "conduction", + "dev", + "nextcloud", + "php-cs-fixer" + ], + "support": { + "docs": "https://docs.conduction.nl/WayOfWork/ci-cd/", + "issues": "https://github.com/ConductionNL/coding-standard/issues", + "source": "https://github.com/ConductionNL/coding-standard/tree/v1.0.0" + }, + "time": "2026-08-12T08:27:21+00:00" + }, + { + "name": "conduction/hydra-gates", + "version": "v1.7.3", + "source": { + "type": "git", + "url": "https://github.com/ConductionNL/.github.git", + "reference": "9b9896abf87167e97b821d8ee86c5422f0b32e80" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ConductionNL/.github/zipball/9b9896abf87167e97b821d8ee86c5422f0b32e80", + "reference": "9b9896abf87167e97b821d8ee86c5422f0b32e80", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "bin": [ + "hydra-gates/bin/hydra-gates" + ], + "type": "library", + "extra": { + "hydra-gates": { + "runner": "hydra-gates/scripts/run-hydra-gates.sh", + "helpers": "hydra-gates/scripts/lib", + "schemas": "hydra-gates/scripts/schemas" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "EUPL-1.2" + ], + "authors": [ + { + "name": "Conduction", + "homepage": "https://conduction.nl" + } + ], + "description": "Hydra's mechanical quality gates, packaged so any repo can run them against its own diff. The exit code is the failure COUNT.", + "homepage": "https://github.com/ConductionNL/.github/tree/main/hydra-gates", + "keywords": [ + "conduction", + "gates", + "nextcloud", + "quality", + "static-analysis" + ], + "support": { + "docs": "https://github.com/ConductionNL/.github/blob/main/hydra-gates/README.md", + "issues": "https://github.com/ConductionNL/.github/issues", + "source": "https://github.com/ConductionNL/.github/tree/v1.7.3" + }, + "time": "2026-08-12T22:32:31+00:00" + }, { "name": "consolidation/annotated-command", "version": "4.10.5", @@ -1531,16 +1635,16 @@ }, { "name": "kubawerlos/php-cs-fixer-custom-fixers", - "version": "v3.37.1", + "version": "v3.37.2", "source": { "type": "git", "url": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers.git", - "reference": "e0ec1f602a1d0836909e9079262dbaf58eaf3804" + "reference": "678df979ce743466b42ddb6eea46b3f4c9a7bade" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kubawerlos/php-cs-fixer-custom-fixers/zipball/e0ec1f602a1d0836909e9079262dbaf58eaf3804", - "reference": "e0ec1f602a1d0836909e9079262dbaf58eaf3804", + "url": "https://api.github.com/repos/kubawerlos/php-cs-fixer-custom-fixers/zipball/678df979ce743466b42ddb6eea46b3f4c9a7bade", + "reference": "678df979ce743466b42ddb6eea46b3f4c9a7bade", "shasum": "" }, "require": { @@ -1571,7 +1675,7 @@ "description": "A set of custom fixers for PHP CS Fixer", "support": { "issues": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/issues", - "source": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/tree/v3.37.1" + "source": "https://github.com/kubawerlos/php-cs-fixer-custom-fixers/tree/v3.37.2" }, "funding": [ { @@ -1579,7 +1683,7 @@ "type": "github" } ], - "time": "2026-04-28T16:41:56+00:00" + "time": "2026-05-12T16:22:19+00:00" }, { "name": "league/container", @@ -1776,16 +1880,16 @@ }, { "name": "nextcloud/coding-standard", - "version": "v1.4.0", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/nextcloud/coding-standard.git", - "reference": "8e06808c1423e9208d63d1bd205b9a38bd400011" + "reference": "80547a93236fbb9c783e05f0f0899043851b0dba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nextcloud/coding-standard/zipball/8e06808c1423e9208d63d1bd205b9a38bd400011", - "reference": "8e06808c1423e9208d63d1bd205b9a38bd400011", + "url": "https://api.github.com/repos/nextcloud/coding-standard/zipball/80547a93236fbb9c783e05f0f0899043851b0dba", + "reference": "80547a93236fbb9c783e05f0f0899043851b0dba", "shasum": "" }, "require": { @@ -1815,35 +1919,36 @@ ], "support": { "issues": "https://github.com/nextcloud/coding-standard/issues", - "source": "https://github.com/nextcloud/coding-standard/tree/v1.4.0" + "source": "https://github.com/nextcloud/coding-standard/tree/v1.5.0" }, - "time": "2025-06-19T12:27:27+00:00" + "time": "2026-05-19T18:30:09+00:00" }, { "name": "nextcloud/ocp", - "version": "v31.0.9", + "version": "v34.0.2", "source": { "type": "git", "url": "https://github.com/nextcloud-deps/ocp.git", - "reference": "abd32429d794ede1d92b7b0a88a1070371c907b5" + "reference": "81cbb2c594afe0fa978885bf7accd0440199520a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/abd32429d794ede1d92b7b0a88a1070371c907b5", - "reference": "abd32429d794ede1d92b7b0a88a1070371c907b5", + "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/81cbb2c594afe0fa978885bf7accd0440199520a", + "reference": "81cbb2c594afe0fa978885bf7accd0440199520a", "shasum": "" }, "require": { - "php": "~8.1 || ~8.2 || ~8.3 || ~8.4", + "php": "~8.2 || ~8.3 || ~8.4 || ~8.5", "psr/clock": "^1.0", "psr/container": "^2.0.2", "psr/event-dispatcher": "^1.0", + "psr/http-client": "^1.0.3", "psr/log": "^3.0.2" }, "type": "library", "extra": { "branch-alias": { - "dev-stable31": "31.0.0-dev" + "dev-stable34": "34.0.0-dev" } }, "notification-url": "https://packagist.org/downloads/", @@ -1863,9 +1968,9 @@ "description": "Composer package containing Nextcloud's public OCP API and the unstable NCU API", "support": { "issues": "https://github.com/nextcloud-deps/ocp/issues", - "source": "https://github.com/nextcloud-deps/ocp/tree/v31.0.9" + "source": "https://github.com/nextcloud-deps/ocp/tree/v34.0.2" }, - "time": "2025-07-31T00:57:37+00:00" + "time": "2026-07-16T01:28:13+00:00" }, { "name": "nikic/php-parser", @@ -2461,16 +2566,16 @@ }, { "name": "php-cs-fixer/shim", - "version": "v3.95.1", + "version": "v3.95.18", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/shim.git", - "reference": "f81ccf51ca60cc9dd21358ffba0e79ebd2ebb78a" + "reference": "9b815f2ba5c581faaaec1386dcda4c16d511e6bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/f81ccf51ca60cc9dd21358ffba0e79ebd2ebb78a", - "reference": "f81ccf51ca60cc9dd21358ffba0e79ebd2ebb78a", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/9b815f2ba5c581faaaec1386dcda4c16d511e6bb", + "reference": "9b815f2ba5c581faaaec1386dcda4c16d511e6bb", "shasum": "" }, "require": { @@ -2507,9 +2612,9 @@ "description": "A tool to automatically fix PHP code style", "support": { "issues": "https://github.com/PHP-CS-Fixer/shim/issues", - "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.95.1" + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.95.18" }, - "time": "2026-04-12T17:00:34+00:00" + "time": "2026-07-30T15:46:28+00:00" }, { "name": "phpcsstandards/phpcsextra", @@ -2595,16 +2700,16 @@ }, { "name": "phpcsstandards/phpcsutils", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", - "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55" + "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/c216317e96c8b3f5932808f9b0f1f7a14e3bbf55", - "reference": "c216317e96c8b3f5932808f9b0f1f7a14e3bbf55", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/5f35d9408c54d7b529501f3c688b6eae562aea1f", + "reference": "5f35d9408c54d7b529501f3c688b6eae562aea1f", "shasum": "" }, "require": { @@ -2684,7 +2789,7 @@ "type": "thanks_dev" } ], - "time": "2025-12-08T14:27:58+00:00" + "time": "2026-07-27T10:28:41+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -3747,6 +3852,111 @@ }, "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, { "name": "psr/log", "version": "3.0.2", @@ -3803,18 +4013,19 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "87a281378fdad8f5926efe259f6ca72e7a395e68" + "reference": "3c9ad688ad8826203588ec49363f73f4deb590c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/87a281378fdad8f5926efe259f6ca72e7a395e68", - "reference": "87a281378fdad8f5926efe259f6ca72e7a395e68", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/3c9ad688ad8826203588ec49363f73f4deb590c1", + "reference": "3c9ad688ad8826203588ec49363f73f4deb590c1", "shasum": "" }, "conflict": { "3f/pygmentize": "<1.2", "adaptcms/adaptcms": "<=1.3", - "admidio/admidio": "<5.0.8", + "adawolfa/isdoc": "<1.4.3|>=1.5,<1.5.1|>=1.6,<1.6.1", + "admidio/admidio": "<=5.0.11", "adodb/adodb-php": "<=5.22.9", "aheinze/cockpit": "<2.2", "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.07.2", @@ -3825,6 +4036,7 @@ "aimeos/aimeos-core": ">=2022.04.1,<2022.10.17|>=2023.04.1,<2023.10.17|>=2024.04.1,<2024.04.7", "aimeos/aimeos-laravel": "==2021.10", "aimeos/aimeos-typo3": "<19.10.12|>=20,<20.10.5", + "aimeos/pagible": "<0.10.4", "airesvsg/acf-to-rest-api": "<=3.1", "akaunting/akaunting": "<2.1.13", "akeneo/pim-community-dev": "<5.0.119|>=6,<6.0.53", @@ -3847,8 +4059,10 @@ "aoe/restler": "<1.7.1", "apache-solr-for-typo3/solr": "<2.8.3", "apereo/phpcas": "<1.6", - "api-platform/core": "<3.4.17|>=4,<4.0.22|>=4.1,<4.1.5", + "api-platform/core": "<4.1.29|>=4.2,<4.2.25|>=4.3,<4.3.8", "api-platform/graphql": "<3.4.17|>=4,<4.0.22|>=4.1,<4.1.5", + "api-platform/hal": ">=4,<4.1.29|>=4.2,<4.2.25|>=4.3,<4.3.8", + "api-platform/json-api": ">=4,<4.1.29|>=4.2,<4.2.25|>=4.3,<4.3.8", "appwrite/server-ce": "<=1.2.1", "arc/web": "<3", "area17/twill": "<1.2.5|>=2,<2.5.3", @@ -3861,21 +4075,21 @@ "austintoddj/canvas": "<=3.4.2", "auth0/auth0-php": ">=3.3,<=8.18", "auth0/login": "<=7.20", - "auth0/symfony": "<=5.7", + "auth0/symfony": "<=5.8", "auth0/wordpress": "<=5.5", - "automad/automad": "<2.0.0.0-alpha5", + "automad/automad": "<=2.0.0.0-beta27", "automattic/jetpack": "<9.8", "awesome-support/awesome-support": "<=6.0.7", "aws/aws-sdk-php": "<=3.371.3", "ayacoo/redirect-tab": "<2.1.2|>=3,<3.1.7|>=4,<4.0.5", - "azuracast/azuracast": "<=0.23.3", + "azuracast/azuracast": "<=0.23.5", "b13/seo_basics": "<0.8.2", "backdrop/backdrop": "<=1.32", - "backpack/crud": "<3.4.9", + "backpack/crud": "<4.0.63|>=4.1,<4.1.69|>=5,<5.0.13", "backpack/filemanager": "<2.0.2|>=3,<3.0.9", "bacula-web/bacula-web": "<9.7.1", "badaso/core": "<=2.9.11", - "bagisto/bagisto": "<2.3.10", + "bagisto/bagisto": "<=2.3.15", "barrelstrength/sprout-base-email": "<1.2.7", "barrelstrength/sprout-forms": "<3.9", "barryvdh/laravel-translation-manager": "<0.6.8", @@ -3888,6 +4102,7 @@ "bedita/bedita": "<4", "bednee/cooluri": "<1.0.30", "bigfork/silverstripe-form-capture": ">=3,<3.1.1", + "billabear/billabear": "<=2025.01.03", "billz/raspap-webgui": "<3.3.6", "binarytorch/larecipe": "<2.8.1", "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", @@ -3908,13 +4123,14 @@ "bytefury/crater": "<6.0.2", "cachethq/cachet": "<2.5.1", "cadmium-org/cadmium-cms": "<=0.4.9", - "cakephp/cakephp": "<3.10.3|>=4,<4.0.10|>=4.1,<4.1.4|>=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10|>=5.2.10,<5.2.12|==5.3", + "cakephp/authentication": "<3.3.6|>=4,<4.1.1", + "cakephp/cakephp": "<4.5.11|>=4.6,<4.6.4|>=5,<5.1.7|>=5.2,<5.2.13|>=5.3,<5.3.6", "cakephp/database": ">=4.2,<4.2.12|>=4.3,<4.3.11|>=4.4,<4.4.10", "cardgate/magento2": "<2.0.33", "cardgate/woocommerce": "<=3.1.15", - "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", + "cart2quote/module-quotation": ">=4.1.6,<4.4.6|>=5,<5.4.4", "cart2quote/module-quotation-encoded": ">=4.1.6,<=4.4.5|>=5,<5.4.4", - "cartalyst/sentry": "<=2.1.6", + "cartalyst/sentry": "<2.1.7", "catfan/medoo": "<1.7.5", "causal/oidc": "<4", "cecil/cecil": "<7.47.1", @@ -3923,41 +4139,42 @@ "cesnet/simplesamlphp-module-proxystatistics": "<3.1", "chriskacerguis/codeigniter-restserver": "<=2.7.1", "chrome-php/chrome": "<1.14", - "ci4-cms-erp/ci4ms": "<0.31.5", + "ci4-cms-erp/ci4ms": "<=0.31.8", "civicrm/civicrm-core": ">=4.2,<4.2.9|>=4.3,<4.3.3", "ckeditor/ckeditor": "<4.25", "clickstorm/cs-seo": ">=6,<6.8|>=7,<7.5|>=8,<8.4|>=9,<9.3", "co-stack/fal_sftp": "<0.2.6", - "cockpit-hq/cockpit": "<2.14", - "code16/sharp": "<9.20", + "cockpit-hq/cockpit": "<=2.14", + "code16/sharp": "<9.22.3", "codeception/codeception": "<3.1.3|>=4,<4.1.22", "codeigniter/framework": "<3.1.10", - "codeigniter4/framework": "<4.6.2", + "codeigniter4/framework": "<4.7.2", "codeigniter4/shield": "<1.0.0.0-beta8", "codiad/codiad": "<=2.8.4", "codingms/additional-tca": ">=1.7,<1.15.17|>=1.16,<1.16.9", "codingms/modules": "<4.3.11|>=5,<5.7.4|>=6,<6.4.2|>=7,<7.5.5", "commerceteam/commerce": ">=0.9.6,<0.9.9", "components/jquery": ">=1.0.3,<3.5", - "composer/composer": "<2.2.27|>=2.3,<2.9.6", - "concrete5/concrete5": "<9.4.8", + "composer/composer": "<2.2.29|>=2.3,<2.10.2", + "concrete5/concrete5": "<9.5.2", "concrete5/core": "<8.5.8|>=9,<9.1", "contao-components/mediaelement": ">=2.14.2,<2.21.1", "contao/comments-bundle": ">=2,<4.13.40|>=5.0.0.0-RC1-dev,<5.3.4", - "contao/contao": ">=3,<3.5.37|>=4,<4.4.56|>=4.5,<4.13.56|>=5,<5.3.38|>=5.4.0.0-RC1-dev,<5.6.1", + "contao/contao": ">=3,<3.5.37|>=4,<4.4.56|>=4.5,<5.3.48|>=5.4,<5.7.9", "contao/core": "<3.5.39", - "contao/core-bundle": "<4.13.57|>=5,<5.3.42|>=5.4,<5.6.5", + "contao/core-bundle": "<5.3.48|>=5.4,<5.7.9", "contao/listing-bundle": ">=3,<=3.5.30|>=4,<4.4.8", "contao/managed-edition": "<=1.5", - "coreshop/core-shop": "<4.1.9", + "coreshop/core-shop": "<4.1.9|==5", "corveda/phpsandbox": "<1.3.5", "cosenary/instagram": "<=2.3", + "cotonti/cotonti": "<=1", "couleurcitron/tarteaucitron-wp": "<0.3", "cpsit/typo3-mailqueue": "<0.4.5|>=0.5,<0.5.2", "craftcms/aws-s3": ">=2.0.2,<=2.2.4", "craftcms/azure-blob": ">=2.0.0.0-beta1,<=2.1", - "craftcms/cms": "<=4.17.8|>=5,<5.9.15", - "craftcms/commerce": ">=4,<4.11|>=5,<5.6", + "craftcms/cms": "<4.18|>=5,<5.10", + "craftcms/commerce": ">=4,<=4.11.1|>=5,<=5.6.4", "craftcms/composer": ">=4.0.0.0-RC1-dev,<=4.10|>=5.0.0.0-RC1-dev,<=5.5.1", "craftcms/craft": ">=3.5,<=4.16.17|>=5.0.0.0-RC1-dev,<=5.8.21", "craftcms/google-cloud": ">=2.0.0.0-beta1,<=2.2", @@ -3975,6 +4192,7 @@ "david-garcia/phpwhois": "<=4.3.1", "dbrisinajumi/d2files": "<1", "dcat/laravel-admin": "<=2.1.3|==2.2.0.0-beta|==2.2.2.0-beta", + "dedoc/scramble": ">=0.13.2,<0.13.22", "derhansen/fe_change_pwd": "<2.0.5|>=3,<3.0.3", "derhansen/sf_event_mgt": "<4.3.1|>=5,<5.1.1|>=7,<7.4", "desperado/xml-bundle": "<=0.1.7", @@ -3996,8 +4214,8 @@ "doctrine/mongodb-odm": "<1.0.2", "doctrine/mongodb-odm-bundle": "<3.0.1", "doctrine/orm": ">=1,<1.2.4|>=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4", - "dolibarr/dolibarr": "<=22.0.4", - "dompdf/dompdf": "<2.0.4", + "dolibarr/dolibarr": "<=23.0.2", + "dompdf/dompdf": "<3.1.6", "doublethreedigital/guest-entries": "<3.1.2", "dreamfactory/df-core": "<1.0.4", "drupal-pattern-lab/unified-twig-extensions": "<=0.1", @@ -4011,7 +4229,7 @@ "drupal/commerce_alphabank_redirect": "<1.0.3", "drupal/commerce_eurobank_redirect": "<2.1.1", "drupal/config_split": "<1.10|>=2,<2.0.2", - "drupal/core": ">=6,<6.38|>=7,<7.103|>=8,<10.4.9|>=10.5,<10.5.6|>=11,<11.1.9|>=11.2,<11.2.8", + "drupal/core": ">=6,<6.38|>=7,<7.103|>=8,<10.5.10|>=10.6,<10.6.9|>=11,<11.2.12|>=11.3,<11.3.10", "drupal/core-recommended": ">=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", "drupal/currency": "<3.5", "drupal/drupal": ">=5,<5.11|>=6,<6.38|>=7,<7.102|>=8,<10.2.11|>=10.3,<10.3.9|>=11,<11.0.8", @@ -4038,10 +4256,11 @@ "drupal/umami_analytics": "<1.0.1", "duncanmcclean/guest-entries": "<3.1.2", "dweeves/magmi": "<=0.7.24", + "easycorp/easyadmin-bundle": ">=4,<4.29.10|>=5,<5.0.13", "ec-cube/ec-cube": "<2.4.4|>=2.11,<=2.17.1|>=3,<=3.0.18.0-patch4|>=4,<=4.3.1", "ecodev/newsletter": "<=4", "ectouch/ectouch": "<=2.7.2", - "egroupware/egroupware": "<23.1.20260113|>=26.0.20251208,<26.0.20260113", + "egroupware/egroupware": "<23.1.20260601|>=26.0.20251208,<26.5.20260507", "elefant/cms": "<2.0.7", "elgg/elgg": "<3.3.24|>=4,<4.0.5", "elijaa/phpmemcacheadmin": "<=1.3", @@ -4053,6 +4272,7 @@ "erusev/parsedown": "<1.7.2", "ether/logs": "<3.0.4", "evolutioncms/evolution": "<=3.2.3", + "evoweb/sf-register": "<13.2.4|>=14,<14.0.2", "exceedone/exment": "<4.4.3|>=5,<5.0.3", "exceedone/laravel-admin": "<2.2.3|==3", "ezsystems/demobundle": ">=5.4,<5.4.6.1-dev", @@ -4075,15 +4295,16 @@ "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", "ezyang/htmlpurifier": "<=4.2", "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", - "facturascripts/facturascripts": "<2025.81", + "facturascripts/facturascripts": "<=2026.2", "fastly/magento2": "<1.2.26", "feehi/cms": "<=2.1.1", "feehi/feehicms": "<=2.1.1", "fenom/fenom": "<=2.12.1", - "filament/actions": ">=3.2,<3.2.123", - "filament/filament": ">=4,<4.3.1", - "filament/infolists": ">=3,<3.2.115", - "filament/tables": ">=3,<3.2.115|>=4,<4.8.5|>=5,<5.3.5", + "filament/actions": ">=3.2,<3.2.123|>=4,<=4.11.3|>=5,<=5.6.3", + "filament/filament": ">=3,<=3.3.51|>=4,<4.11.5|>=5,<5.6.5", + "filament/forms": ">=3,<=3.3.52", + "filament/infolists": ">=3,<3.2.115|>=4,<=4.11.4|>=5,<=5.6.4", + "filament/tables": ">=3,<=3.3.50|>=4,<=4.11.4|>=5,<=5.6.4", "filegator/filegator": "<7.8", "filp/whoops": "<2.1.13", "fineuploader/php-traditional-server": "<=1.2.2", @@ -4098,6 +4319,7 @@ "flarum/nicknames": "<1.8.3", "flarum/sticky": ">=0.1.0.0-beta14,<=0.1.0.0-beta15", "flarum/tags": "<=0.1.0.0-beta13", + "flightphp/core": "<3.18.1", "floriangaerber/magnesium": "<0.3.1", "fluidtypo3/vhs": "<5.1.1", "fof/byobu": ">=0.3.0.0-beta2,<1.1.7", @@ -4116,19 +4338,22 @@ "friendsofsymfony1/symfony1": ">=1.1,<1.5.19", "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", "friendsoftypo3/openid": ">=4.5,<4.5.31|>=4.7,<4.7.16|>=6,<6.0.11|>=6.1,<6.1.6", + "friendsoftypo3/tt-address": "<8.1.2|>=9,<9.1.1|>=10,<10.0.1", "froala/wysiwyg-editor": "<=4.3", "frosh/adminer-platform": "<2.2.1", - "froxlor/froxlor": "<2.3.6", + "froxlor/froxlor": "<2.3.7", "frozennode/administrator": "<=5.0.12", "fuel/core": "<1.8.1", - "funadmin/funadmin": "<=7.1.0.0-RC4", + "funadmin/funadmin": "<=7.1.0.0-RC6", "gaoming13/wechat-php-sdk": "<=1.10.2", "genix/cms": "<=1.1.11", - "georgringer/news": "<1.3.3", + "georgringer/news": "<10.0.4|>=11,<11.4.4|>=12,<12.3.2|>=13,<13.0.2|>=14,<14.0.3", "geshi/geshi": "<=1.0.9.1", "getformwork/formwork": "<=2.3.3", - "getgrav/grav": "<1.11.0.0-beta1", - "getkirby/cms": "<5.4", + "getgrav/grav": "<=2.0.0.0-RC8", + "getgrav/grav-plugin-api": "<1.0.0.0-beta15", + "getgrav/grav-plugin-form": "<9.1", + "getkirby/cms": "<=4.9.3|>=5,<=5.4.3", "getkirby/kirby": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1", "getkirby/panel": "<2.5.14", "getkirby/starterkit": "<=3.7.0.2", @@ -4143,11 +4368,12 @@ "gp247/core": "<1.1.24", "gree/jose": "<2.2.1", "gregwar/rst": "<1.0.3", - "grumpydictator/firefly-iii": "<6.1.17|>=6.4.23,<=6.5", + "grumpydictator/firefly-iii": "<=6.6.2", "gugoan/economizzer": "<=0.9.0.0-beta1", - "guzzlehttp/guzzle": "<6.5.8|>=7,<7.4.5", + "guzzlehttp/guzzle": "<7.15.1", + "guzzlehttp/guzzle-services": "<1.5.4", "guzzlehttp/oauth-subscriber": "<0.8.1", - "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", + "guzzlehttp/psr7": "<2.12.3", "haffner/jh_captcha": "<=2.1.3|>=3,<=3.0.2", "handcraftedinthealps/goodby-csv": "<1.4.3", "harvesthq/chosen": "<1.8.7", @@ -4176,6 +4402,7 @@ "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<6.18.31|>=7,<7.22.4", "illuminate/database": "<6.20.26|>=7,<7.30.5|>=8,<8.40", "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", + "illuminate/mail": ">=9,<12.60|>=13,<13.10", "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", "imdbphp/imdbphp": "<=5.1.1", "impresscms/impresscms": "<=1.4.5", @@ -4187,8 +4414,9 @@ "innologi/typo3-appointments": "<2.0.6", "intelliants/subrion": "<4.2.2", "inter-mediator/inter-mediator": "==5.5", + "intercom/intercom-php": "==5.0.2", "invoiceninja/invoiceninja": "<5.13.4", - "ipl/web": "<0.10.1", + "ipl/web": "<=0.10.2|>=0.11,<=0.13", "islandora/crayfish": "<4.1", "islandora/islandora": ">=2,<2.4.1", "ivankristianto/phpwhois": "<=4.3", @@ -4200,6 +4428,7 @@ "jasig/phpcas": "<1.3.3", "jbartels/wec-map": "<3.0.3", "jcbrand/converse.js": "<3.3.3", + "jleehr/canto-saas-api": "<=2", "joedolson/my-calendar": "<3.7.7", "joelbutcher/socialstream": "<5.6|>=6,<6.2", "johnbillion/query-monitor": "<3.20.4", @@ -4226,23 +4455,24 @@ "kelvinmo/simplexrd": "<3.1.1", "kevinpapst/kimai2": "<1.16.7", "khodakhah/nodcms": "<=3.4.1", - "kimai/kimai": "<2.54", + "kimai/kimai": "<2.59", "kitodo/presentation": "<3.2.3|>=3.3,<3.3.4", "klaviyo/magento2-extension": ">=1,<3", - "knplabs/knp-snappy": "<=1.4.2", + "knplabs/knp-snappy": "<=1.7", "kohana/core": "<3.3.3", "koillection/koillection": "<1.6.12", "krayin/laravel-crm": "<=2.2", "kreait/firebase-php": ">=3.2,<3.8.1", "kumbiaphp/kumbiapp": "<=1.1.1", "la-haute-societe/tcpdf": "<6.2.22", + "laktak/hjson": "<2.3", "laminas/laminas-diactoros": "<2.18.1|==2.19|==2.20|==2.21|==2.22|==2.23|>=2.24,<2.24.2|>=2.25,<2.25.2", "laminas/laminas-form": "<2.17.1|>=3,<3.0.2|>=3.1,<3.1.1", "laminas/laminas-http": "<2.14.2", "lara-zeus/artemis": ">=1,<=1.0.6", "lara-zeus/dynamic-dashboard": ">=3,<=3.0.1", "laravel/fortify": "<1.11.1", - "laravel/framework": "<10.48.29|>=11,<11.44.1|>=12,<12.1.1", + "laravel/framework": "<12.61.1|>=13,<13.12", "laravel/laravel": ">=5.4,<5.4.22", "laravel/passport": ">=13,<13.7.1", "laravel/pulse": "<1.3.1", @@ -4261,7 +4491,7 @@ "librenms/librenms": "<26.3", "liftkit/database": "<2.13.2", "lightsaml/lightsaml": "<1.3.5", - "limesurvey/limesurvey": "<6.15.4", + "limesurvey/limesurvey": "<=7.0.0.0-beta1", "livehelperchat/livehelperchat": "<=3.91", "livewire-filemanager/filemanager": "<=1.0.4", "livewire/livewire": "<2.12.7|>=3.0.0.0-beta1,<3.6.4", @@ -4284,21 +4514,23 @@ "maikuolan/phpmussel": ">=1,<1.6", "mainwp/mainwp": "<=4.4.3.3", "manogi/nova-tiptap": "<=3.2.6", - "mantisbt/mantisbt": "<2.28.1", + "mantisbt/mantisbt": "<=2.28.3", "marcwillmann/turn": "<0.3.3", "markhuot/craftql": "<=1.3.7", "marshmallow/nova-tiptap": "<5.7", "matomo/matomo": "<1.11", "matyhtf/framework": "<3.0.6", - "mautic/core": "<5.2.10|>=6,<6.0.8|>=7.0.0.0-alpha,<7.0.1", + "mautic/core": "<5.2.11|>=6,<6.0.9|>=7,<7.1.2", "mautic/core-lib": ">=1.0.0.0-beta,<4.4.13|>=5.0.0.0-alpha,<5.1.1", "mautic/grapes-js-builder-bundle": ">=4,<4.4.18|>=5,<5.2.9|>=6,<6.0.7", "maximebf/debugbar": "<1.19", + "mckenziearts/livewire-markdown-editor": "<1.3", "mdanter/ecc": "<2", "mediawiki/abuse-filter": "<1.39.9|>=1.40,<1.41.3|>=1.42,<1.42.2", "mediawiki/cargo": "<3.8.3", "mediawiki/core": "<1.39.5|==1.40", "mediawiki/data-transfer": ">=1.39,<1.39.11|>=1.41,<1.41.3|>=1.42,<1.42.2", + "mediawiki/maps": "<12.1.3", "mediawiki/matomo": "<2.4.3", "mediawiki/semantic-media-wiki": "<4.0.2", "mehrwert/phpmyadmin": "<3.2", @@ -4318,6 +4550,8 @@ "miniorange/miniorange-saml": "<1.4.3", "miraheze/ts-portal": "<=33", "mittwald/typo3_forum": "<1.2.1", + "mix/mix": ">=2,<=2.2.17", + "mmc/ceselector": "<3.0.3|>=4,<4.0.2|>=5,<5.0.1|>=6,<6.0.1", "mobiledetect/mobiledetectlib": "<2.8.32", "modx/revolution": "<=3.1", "mojo42/jirafeau": "<4.4", @@ -4330,6 +4564,7 @@ "movim/moxl": ">=0.8,<=0.10", "movingbytes/social-network": "<=1.2.1", "mpdf/mpdf": "<=7.1.7", + "mtdowling/jmespath.php": "<2.9.1", "munkireport/comment": "<4", "munkireport/managedinstalls": "<2.6", "munkireport/munki_facts": "<1.5", @@ -4337,6 +4572,7 @@ "munkireport/softwareupdate": "<1.6", "mustache/mustache": ">=2,<2.14.1", "mwdelaney/wp-enable-svg": "<=0.2", + "nabeel/phpvms": "<7.0.6", "namshi/jose": "<2.2", "nasirkhan/laravel-starter": "<11.11", "nategood/httpful": "<1", @@ -4356,11 +4592,11 @@ "nilsteampassnet/teampass": "<3.1.3.1-dev", "nitsan/ns-backup": "<13.0.1", "nonfiction/nterchange": "<4.1.1", - "notrinos/notrinos-erp": "<=0.7", + "notrinos/notrinos-erp": "<=1", "noumo/easyii": "<=0.9", "novaksolutions/infusionsoft-php-sdk": "<1", "novosga/novosga": "<=2.2.12", - "nukeviet/nukeviet": "<4.5.02", + "nukeviet/nukeviet": "<4.6.00", "nyholm/psr7": "<1.6.1", "nystudio107/craft-seomatic": "<3.4.12", "nzedb/nzedb": "<0.8", @@ -4377,7 +4613,7 @@ "open-web-analytics/open-web-analytics": "<1.8.1", "opencart/opencart": ">=0", "openid/php-openid": "<2.3", - "openmage/magento-lts": "<20.17", + "openmage/magento-lts": "<=20.17", "opensolutions/vimbadmin": "<=3.0.15", "opensource-workshop/connect-cms": "<1.41.1|>=2,<2.41.1", "orchid/platform": ">=8,<14.43", @@ -4388,8 +4624,10 @@ "oro/customer-portal": ">=4.1,<=4.1.13|>=4.2,<=4.2.10|>=5,<=5.0.11|>=5.1,<=5.1.3", "oro/platform": ">=1.7,<1.7.4|>=3.1,<3.1.29|>=4.1,<4.1.17|>=4.2,<=4.2.10|>=5,<=5.0.12|>=5.1,<=5.1.3", "oveleon/contao-cookiebar": "<1.16.3|>=2,<2.1.3", - "oxid-esales/oxideshop-ce": "<=7.0.5", + "oxid-esales/oxideshop-ce": "<4.5|>=6,<6.14.4", + "oxid-esales/oxideshop-metapackage-ce": ">=6,<6.5.5", "oxid-esales/paymorrow-module": ">=1,<1.0.2|>=2,<2.0.1", + "oxid-esales/smarty-component": "<1.0.1", "packbackbooks/lti-1-3-php-library": "<5", "padraic/humbug_get_contents": "<1.1.2", "pagarme/pagarme-php": "<3", @@ -4398,6 +4636,7 @@ "paragonie/random_compat": "<2", "paragonie/sodium_compat": "<1.24|>=2,<2.5", "passbolt/passbolt_api": "<4.6.2", + "paymenter/paymenter": "<=1.5.4", "paypal/adaptivepayments-sdk-php": "<=3.9.2", "paypal/invoice-sdk-php": "<=3.9", "paypal/merchant-sdk-php": "<3.12", @@ -4410,23 +4649,26 @@ "pegasus/google-for-jobs": "<1.5.1|>=2,<2.1.1", "personnummer/personnummer": "<3.0.2", "ph7software/ph7builder": "<=17.9.1", - "phanan/koel": "<5.1.4", + "phanan/koel": "<=9.7", + "pheditor/pheditor": "<2.0.8", "phenx/php-svg-lib": "<0.5.2", "php-censor/php-censor": "<2.0.13|>=2.1,<2.1.5", "php-mod/curl": "<2.3.2", - "phpbb/phpbb": "<3.3.11", + "php-standard-library/h2": ">=6.1,<6.1.2|>=6.2,<6.2.1", + "php-standard-library/php-standard-library": ">=6.1,<6.1.2|>=6.2,<6.2.1", + "phpbb/phpbb": "<3.3.16|==4.0.0.0-alpha1", "phpems/phpems": ">=6,<=6.1.3", "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", "phpmailer/phpmailer": "<6.5", "phpmussel/phpmussel": ">=1,<1.6", "phpmyadmin/phpmyadmin": "<5.2.2", - "phpmyfaq/phpmyfaq": "<=4.1", + "phpmyfaq/phpmyfaq": "<4.1.4", "phpoffice/common": "<0.2.9", "phpoffice/math": "<=0.2", "phpoffice/phpexcel": "<=1.8.2", - "phpoffice/phpspreadsheet": "<=1.30.3|>=2,<=2.1.15|>=2.2,<=2.4.4|>=3,<=3.10.4|>=4,<=5.6", + "phpoffice/phpspreadsheet": "<=1.30.5|>=2,<=2.1.17|>=2.2,<=2.4.6|>=3,<=3.10.6|>=4,<=5.8", "phppgadmin/phppgadmin": "<=7.13", - "phpseclib/phpseclib": "<2.0.53|>=3,<3.0.51", + "phpseclib/phpseclib": "<=2.0.54|>=3,<=3.0.53", "phpservermon/phpservermon": "<3.6", "phpsysinfo/phpsysinfo": "<3.4.3", "phpunit/phpunit": "<8.5.52|>=9,<9.6.33|>=10,<10.5.62|>=11,<11.5.50|>=12,<12.5.8|>=12.5.21,<12.5.22|>=13.1.5,<13.1.6", @@ -4435,14 +4677,14 @@ "phpxmlrpc/phpxmlrpc": "<4.9.2", "phraseanet/phraseanet": "==4.0.3", "pi/pi": "<=2.5", - "pimcore/admin-ui-classic-bundle": "<=1.7.15|>=2.0.0.0-RC1-dev,<=2.2.2", + "pimcore/admin-ui-classic-bundle": "<1.7.18|>=2.0.0.0-RC1-dev,<=2.3.5", "pimcore/customer-management-framework-bundle": "<4.2.1", "pimcore/data-hub": "<1.2.4", "pimcore/data-importer": "<1.8.9|>=1.9,<1.9.3", "pimcore/demo": "<10.3", "pimcore/ecommerce-framework-bundle": "<1.0.10", "pimcore/perspective-editor": "<1.5.1", - "pimcore/pimcore": "<=11.5.14.1|>=12,<12.3.3", + "pimcore/pimcore": "<=12.3.8|>=2026.1,<2026.1.3", "pimcore/web2print-tools-bundle": "<=5.2.1|>=6.0.0.0-RC1-dev,<=6.1", "piwik/piwik": "<1.11", "pixelfed/pixelfed": "<0.12.5", @@ -4450,25 +4692,27 @@ "pocketmine/bedrock-protocol": "<8.0.2", "pocketmine/pocketmine-mp": "<5.42.1", "pocketmine/raklib": ">=0.14,<0.14.6|>=0.15,<0.15.1", + "pontedilana/php-weasyprint": "<=2.5.1", + "poweradmin/poweradmin": "<4.2.5|>=4.3,<4.3.4", "pressbooks/pressbooks": "<5.18", "prestashop/autoupgrade": ">=4,<4.10.1", "prestashop/blockreassurance": "<=5.1.3", "prestashop/blockwishlist": ">=2,<2.1.1", "prestashop/contactform": ">=1.0.1,<4.3", "prestashop/gamification": "<2.3.2", - "prestashop/prestashop": "<8.2.5|>=9.0.0.0-alpha1,<9.1", + "prestashop/prestashop": "<8.2.6|>=9,<9.1.1", "prestashop/productcomments": "<5.0.2", - "prestashop/ps_checkout": "<4.4.1|>=5,<5.0.5", + "prestashop/ps_checkout": "<5.3", "prestashop/ps_contactinfo": "<=3.3.2", "prestashop/ps_emailsubscription": "<2.6.1", - "prestashop/ps_facetedsearch": "<3.4.1", + "prestashop/ps_facetedsearch": "<4.0.4", "prestashop/ps_linklist": "<3.1", "privatebin/privatebin": "<1.4|>=1.5,<1.7.4|>=1.7.7,<2.0.3", "processwire/processwire": "<=3.0.255", - "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", - "propel/propel1": ">=1,<=1.7.1", + "propel/propel": ">=2.0.0.0-alpha1,<2.0.0.0-alpha8", + "propel/propel1": ">=1,<1.7.2", "psy/psysh": "<=0.11.22|>=0.12,<=0.12.18", - "pterodactyl/panel": "<1.12.1", + "pterodactyl/panel": "<=1.12.4", "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", "ptrofimov/beanstalk_console": "<1.7.14", "pubnub/pubnub": "<6.1", @@ -4488,13 +4732,14 @@ "rap2hpoutre/laravel-log-viewer": "<0.13", "react/http": ">=0.7,<1.9", "really-simple-plugins/complianz-gdpr": "<6.4.2", - "redaxo/source": "<5.21", + "redaxo/source": "<5.21.1", "remdex/livehelperchat": "<4.29", "renolit/reint-downloadmanager": "<4.0.2|>=5,<5.0.1", "reportico-web/reportico": "<=8.1", "rhukster/dom-sanitizer": "<1.0.10", "rmccue/requests": ">=1.6,<1.8", "roadiz/documents": "<2.3.42|>=2.4,<2.5.44|>=2.6,<2.6.28|>=2.7,<2.7.9", + "roadiz/openid": "<2.3.43|>=2.5,<2.5.45|>=2.6,<2.6.31|>=2.7,<2.7.18", "robrichards/xmlseclibs": "<3.1.5", "roots/soil": "<4.1", "roundcube/roundcubemail": "<1.5.10|>=1.6,<1.6.11|>=1.7.0.0-beta,<1.7.0.0-RC5-dev", @@ -4510,24 +4755,26 @@ "scheb/two-factor-bundle": "<3.26|>=4,<4.11", "sensiolabs/connect": "<4.2.3", "serluck/phpwhois": "<=4.2.6", - "setasign/fpdi": "<2.6.4", + "setasign/fpdi": "<2.6.7", "sfroemken/url_redirect": "<=1.2.1", "sheng/yiicms": "<1.2.1", - "shopware/core": "<6.6.10.15-dev|>=6.7,<6.7.8.1-dev", - "shopware/platform": "<6.6.10.15-dev|>=6.7,<6.7.8.1-dev", + "shopper/cart": "<2.8", + "shopper/framework": "<2.8", + "shopware/core": "<6.6.10.18-dev|>=6.7,<6.7.10.1-dev", + "shopware/platform": "<6.6.10.18-dev|>=6.7,<6.7.10.1-dev", "shopware/production": "<=6.3.5.2", - "shopware/shopware": "<=5.7.17|>=6.4.6,<6.6.10.10-dev|>=6.7,<6.7.6.1-dev", + "shopware/shopware": "<=6.3.5.2|>=6.4.6,<6.6.10.10-dev|>=6.7,<6.7.6.1-dev", "shopware/storefront": "<6.6.10.10-dev|>=6.7,<6.7.5.1-dev", "shopxo/shopxo": "<=6.4", - "showdoc/showdoc": "<2.10.4", + "showdoc/showdoc": "<3.8.1", "shuchkin/simplexlsx": ">=1.0.12,<1.1.13", "silverstripe-australia/advancedreports": ">=1,<=2", "silverstripe/admin": "<1.13.19|>=2,<2.1.8", "silverstripe/assets": "<2.4.5|>=3,<3.1.3", - "silverstripe/cms": "<4.11.3", + "silverstripe/cms": "<6.2.1", "silverstripe/comments": ">=1.3,<3.1.1", - "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": "<5.3.23", + "silverstripe/forum": "<0.6.2|>=0.7,<0.7.4", + "silverstripe/framework": "<6.2.2", "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3", "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", "silverstripe/recipe-cms": ">=4.5,<4.5.3", @@ -4537,13 +4784,15 @@ "silverstripe/silverstripe-omnipay": "<2.5.2|>=3,<3.0.2|>=3.1,<3.1.4|>=3.2,<3.2.1", "silverstripe/subsites": ">=2,<2.6.1", "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", - "silverstripe/userforms": "<3|>=5,<5.4.2", + "silverstripe/userforms": "<6.4.9|>=7,<7.0.7|>=7.1,<7.1.1", + "silverstripe/versioned": "<3.2.1", "silverstripe/versioned-admin": ">=1,<1.11.1", "simogeo/filemanager": "<=2.5", "simple-updates/phpwhois": "<=1", - "simplesamlphp/saml2": "<=4.16.15|>=5.0.0.0-alpha1,<=5.0.0.0-alpha19", - "simplesamlphp/saml2-legacy": "<=4.16.15", - "simplesamlphp/simplesamlphp": "<1.18.6", + "simplesamlphp/saml2": "<=4.20.2|>=5,<5.0.6|>=6,<6.2.1", + "simplesamlphp/saml2-legacy": "<=4.20.2", + "simplesamlphp/simplesamlphp": "<=2.4.6|>=2.5,<=2.5.1", + "simplesamlphp/simplesamlphp-module-casserver": "<=7.0.2", "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", "simplesamlphp/simplesamlphp-module-openid": "<1", "simplesamlphp/simplesamlphp-module-openidprovider": "<0.9", @@ -4555,19 +4804,23 @@ "sjbr/sr-freecap": "<2.4.6|>=2.5,<2.5.3", "sjbr/static-info-tables": "<2.3.1", "slim/psr7": "<1.4.1|>=1.5,<1.5.1|>=1.6,<1.6.1", - "slim/slim": "<2.6", + "slim/slim": "<2.6|>=4.4,<=4.15.1", "slub/slub-events": "<3.0.3", "smarty/smarty": "<4.5.3|>=5,<5.1.1", - "snipe/snipe-it": "<8.3.7", + "snipe/snipe-it": "<=8.6.1", "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", + "solidinvoice/solidinvoice": "<=2.3.15", "solspace/craft-freeform": "<4.1.29|>=5,<=5.14.6", "soosyze/soosyze": "<=2", "spatie/browsershot": "<5.0.5", "spatie/image-optimizer": "<1.7.3", + "spatie/laravel-medialibrary": "<11.23", + "spatie/schema-org": ">=3.23.1,<3.23.2|>=4,<4.0.2", "spencer14420/sp-php-email-handler": "<1", "spipu/html2pdf": "<5.2.8", "spiral/roadrunner": "<2025.1", + "spomky-labs/otphp": "<11.4.3", "spoon/library": "<1.4.1", "spoonity/tcpdf": "<6.2.22", "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", @@ -4576,14 +4829,14 @@ "starcitizentools/short-description": ">=4,<4.0.1", "starcitizentools/tabber-neue": ">=1.9.1,<2.7.2|>=3,<3.1.1", "starcitizenwiki/embedvideo": "<=4", - "statamic/cms": "<5.73.20|>=6,<6.13", + "statamic/cms": "<5.74|>=6,<6.20.3", "stormpath/sdk": "<9.9.99", - "studio-42/elfinder": "<2.1.67", + "studio-42/elfinder": "<=2.1.67", "studiomitte/friendlycaptcha": "<0.1.4", "subhh/libconnect": "<7.0.8|>=8,<8.1", "sukohi/surpass": "<1", "sulu/form-bundle": ">=2,<2.5.3", - "sulu/sulu": "<2.6.22|>=3,<3.0.5", + "sulu/sulu": "<=2.6.22|>=3,<=3.0.5", "sumocoders/framework-user-bundle": "<1.4", "superbig/craft-audit": "<3.0.2", "svewap/a21glossary": "<=0.4.10", @@ -4593,50 +4846,65 @@ "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", "sylius/grid-bundle": "<1.10.1", + "sylius/mollie-plugin": "<2.2.8|>=3,<3.2.4|>=3.3,<3.3.1", "sylius/paypal-plugin": "<1.6.2|>=1.7,<1.7.2|>=2,<2.0.2", "sylius/resource-bundle": ">=1,<1.3.14|>=1.4,<1.4.7|>=1.5,<1.5.2|>=1.6,<1.6.4", - "sylius/sylius": "<1.9.12|>=1.10,<1.10.16|>=1.11,<1.11.17|>=1.12,<=1.12.22|>=1.13,<=1.13.14|>=1.14,<=1.14.17|>=2,<=2.0.15|>=2.1,<=2.1.11|>=2.2,<=2.2.2", + "sylius/sylius": "<1.9.12|>=1.10,<1.10.16|>=1.11,<1.11.17|>=1.12,<=1.12.22|>=1.13,<=1.13.14|>=1.14,<=1.14.17|>=2,<2.0.18|>=2.1,<2.1.15|>=2.2,<2.2.6", + "symbiote/silverstripe-advancedworkflow": "<6.4.5|>=7,<7.1.3|>=7.2,<7.2.1", "symbiote/silverstripe-multivaluefield": ">=3,<3.1", "symbiote/silverstripe-queuedjobs": ">=3,<3.0.2|>=3.1,<3.1.4|>=4,<4.0.7|>=4.1,<4.1.2|>=4.2,<4.2.4|>=4.3,<4.3.3|>=4.4,<4.4.3|>=4.5,<4.5.1|>=4.6,<4.6.4", "symbiote/silverstripe-seed": "<6.0.3", "symbiote/silverstripe-versionedfiles": "<=2.0.3", "symfont/process": ">=0", - "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", + "symfony/cache": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/dom-crawler": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4", - "symfony/http-client": ">=4.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", - "symfony/http-foundation": "<5.4.50|>=6,<6.4.29|>=7,<7.3.7", - "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/html-sanitizer": ">=6.1,<6.4.41|>=7,<7.4.13|>=8,<8.0.13", + "symfony/http-client": ">=4.3,<5.4.53|>=6,<6.4.15|>=7,<7.1.8", + "symfony/http-foundation": "<5.4.50|>=6,<6.4.41|>=7,<7.4.13|>=8,<8.0.13", + "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6|>=7.4,<7.4.12|>=8,<8.0.12", "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/json-path": ">=7.3,<7.4.12|>=8,<8.0.12", + "symfony/lox24-notifier": ">=7.1,<7.4.12|>=8,<8.0.12", + "symfony/mailer": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/mailjet-mailer": ">=6.4,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/mailomat-mailer": ">=7.2,<7.4.13|>=8,<8.0.13", + "symfony/mailtrap-mailer": ">=7.2,<7.4.12|>=8,<8.0.12", "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", - "symfony/mime": ">=4.3,<4.3.8", + "symfony/mime": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/monolog-bridge": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/polyfill": ">=1,<1.10", + "symfony/polyfill": ">=1,<1.10|>=1.17.1,<1.38.1", + "symfony/polyfill-intl-idn": ">=1.17.1,<1.38.1", "symfony/polyfill-php55": ">=1,<1.10", "symfony/process": "<5.4.51|>=6,<6.4.33|>=7,<7.1.7|>=7.3,<7.3.11|>=7.4,<7.4.5|>=8,<8.0.5", "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", - "symfony/routing": ">=2,<2.0.19", - "symfony/runtime": ">=5.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7", + "symfony/routing": "<5.4.53|>=6,<6.4.41|>=7,<7.4.13|>=8,<8.0.13", + "symfony/runtime": ">=5.3,<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.4.10|>=7,<7.0.10|>=7.1,<7.1.3", "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", - "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", + "symfony/security-http": "<5.4.53|>=6,<6.4.41|>=7,<7.4.13|>=8,<8.0.13", "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", - "symfony/symfony": "<5.4.51|>=6,<6.4.33|>=7,<7.3.11|>=7.4,<7.4.5|>=8,<8.0.5", + "symfony/symfony": "<5.4.53|>=6,<6.4.41|>=7,<7.4.13|>=8,<8.0.13", "symfony/translation": ">=2,<2.0.17", - "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", - "symfony/ux-autocomplete": "<2.11.2", - "symfony/ux-live-component": "<2.25.1", + "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8|>=6.4.24,<6.4.40", + "symfony/twilio-notifier": ">=6.4,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/ux-autocomplete": "<2.36|>=3,<3.1", + "symfony/ux-icons": ">=2.17,<2.36.1|>=3,<3.2", + "symfony/ux-live-component": "<2.36|>=3,<3.1", + "symfony/ux-toolkit": ">=2.32,<2.36.1|>=3,<3.2", "symfony/ux-twig-component": "<2.25.1", "symfony/validator": "<5.4.43|>=6,<6.4.11|>=7,<7.1.4", "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", - "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", + "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4|>=7.2.9,<7.4.12|>=8,<8.0.12", "symfony/webhook": ">=6.3,<6.3.8", - "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7|>=2.2.0.0-beta1,<2.2.0.0-beta2", + "symfony/yaml": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symphonycms/symphony-2": "<2.6.4", "t3/dce": "<0.11.5|>=2.2,<2.6.2", "t3g/svg-sanitizer": "<1.0.3", @@ -4647,45 +4915,50 @@ "tecnickcom/tcpdf": "<6.8", "terminal42/contao-tablelookupwizard": "<3.3.5", "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1,<2.1.3", + "thelia/thelia": ">=2.0.0.0-beta1,<2.1.3", "theonedemon/phpwhois": "<=4.2.5", "thinkcmf/thinkcmf": "<6.0.8", - "thorsten/phpmyfaq": "<4.1.1", + "thorsten/phpmyfaq": "<4.1.4", "tikiwiki/tiki-manager": "<=17.1", "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", - "tinymce/tinymce": "<7.2", + "tinymce/tinymce": "<7.9.3|>=8,<8.5.1", "tinymighty/wiki-seo": "<1.2.2", "titon/framework": "<9.9.99", "tltneon/lgsl": "<7", "tobiasbg/tablepress": "<=2.0.0.0-RC1", + "tomasnorre/crawler": "<11.0.13|>=12,<12.0.11", "topthink/framework": "<6.0.17|>=6.1,<=8.0.4", "topthink/think": "<=6.1.1", "topthink/thinkphp": "<=3.2.3|>=6.1.3,<=8.0.4", "torrentpier/torrentpier": "<=2.8.8", - "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", + "tpwd/ke_search": "<5.6.2|>=6,<6.6.1|>=7,<7.0.1", "tribalsystems/zenario": "<=9.7.61188", "truckersmp/phpwhois": "<=4.3.1", "ttskch/pagination-service-provider": "<1", "twbs/bootstrap": "<3.4.1|>=4,<4.3.1", - "twig/twig": "<3.11.2|>=3.12,<3.14.1|>=3.16,<3.19", - "typicms/core": "<16.1.7", + "twig/cssinliner-extra": "<3.26", + "twig/intl-extra": "<3.26", + "twig/markdown-extra": "<3.26", + "twig/twig": "<3.27", + "typicms/core": "<12.0.5|>=13,<13.0.9|>=14,<14.0.27|>=15,<15.0.29|>=16,<16.1.7", "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", - "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1|==14.2", + "typo3/cms-backend": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3", "typo3/cms-belog": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", "typo3/cms-beuser": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", - "typo3/cms-core": "<=8.7.56|>=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", + "typo3/cms-core": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3", "typo3/cms-dashboard": ">=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", "typo3/cms-extensionmanager": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", "typo3/cms-felogin": ">=4.2,<4.2.3", - "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1", - "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-filelist": ">=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3", + "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1|>=8,<8.7.23|>=9,<9.5.4", + "typo3/cms-form": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.5", "typo3/cms-frontend": "<4.3.9|>=4.4,<4.4.5", - "typo3/cms-indexed-search": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-indexed-search": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<13.4.31|>=14,<14.3.3", "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8|==13.4.2", "typo3/cms-lowlevel": ">=11,<=11.5.41", "typo3/cms-recordlist": ">=11,<11.5.48", - "typo3/cms-recycler": ">=9,<9.5.55|>=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", + "typo3/cms-recycler": "<10.4.57|>=11,<11.5.51|>=12,<12.4.46|>=13,<13.4.31|>=14,<14.3.3", "typo3/cms-redirects": ">=10,<=10.4.54|>=11,<=11.5.48|>=12,<=12.4.40|>=13,<=13.4.22|>=14,<=14.0.1", "typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30", "typo3/cms-scheduler": ">=11,<=11.5.41", @@ -4693,7 +4966,7 @@ "typo3/cms-webhooks": ">=12,<=12.4.30|>=13,<=13.4.11", "typo3/cms-workspaces": ">=9,<9.5.55|>=10,<10.4.54|>=11,<11.5.48|>=12,<12.4.37|>=13,<13.4.18", "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", - "typo3/html-sanitizer": ">=1,<=1.5.2|>=2,<=2.1.3", + "typo3/html-sanitizer": "<2.3.2", "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", "typo3/swiftmailer": ">=4.1,<4.1.99|>=5.4,<5.4.5", @@ -4709,7 +4982,7 @@ "uvdesk/core-framework": "<=1.1.1", "vanilla/safecurl": "<0.9.2", "verbb/comments": "<1.5.5", - "verbb/formie": "<=2.1.43", + "verbb/formie": "<3.1.28", "verbb/image-resizer": "<2.0.9", "verbb/knock-knock": "<1.2.8", "verot/class.upload.php": "<=2.1.6", @@ -4723,16 +4996,20 @@ "wallabag/wallabag": "<2.6.11", "wanglelecc/laracms": "<=1.0.3", "wapplersystems/a21glossary": "<=0.4.10", - "web-auth/webauthn-framework": ">=3.3,<3.3.4|>=4.5,<4.9|>=5.2,<5.2.4", - "web-auth/webauthn-lib": ">=4.5,<4.9|>=5.2,<5.2.4", - "web-auth/webauthn-symfony-bundle": ">=5.2,<5.2.4", + "web-auth/webauthn-framework": ">=3.3,<3.3.4|>=4.5,<4.9|>=5.2,<5.2.4|>=5.3,<5.3.1", + "web-auth/webauthn-lib": ">=4.5,<5.3.5", + "web-auth/webauthn-symfony-bundle": "<5.3.4", "web-feet/coastercms": "==5.5", + "web-token/jwt-bundle": "<3.4.10|>=4,<4.0.7|>=4.1,<4.1.7", + "web-token/jwt-experimental": "<4.1.7", + "web-token/jwt-framework": "<4.1.7", + "web-token/jwt-library": "<3.4.10|>=4,<4.0.7|>=4.1,<4.1.7", "web-tp3/wec_map": "<3.0.3", "webbuilders-group/silverstripe-kapost-bridge": "<0.4", "webcoast/deferred-image-processing": "<1.0.2", "webklex/laravel-imap": "<5.3", "webklex/php-imap": "<5.3", - "webonyx/graphql-php": "<=15.31.4", + "webonyx/graphql-php": "<=15.32.2", "webpa/webpa": "<3.1.2", "webreinvent/vaahcms": "<=2.3.1", "wikibase/wikibase": "<=1.39.3", @@ -4744,9 +5021,11 @@ "winter/wn-system-module": "<1.2.4", "wintercms/winter": "<=1.2.3", "wireui/wireui": "<1.19.3|>=2,<2.1.3", + "wnx/laravel-backup-restore": "<=1.9.3", "woocommerce/woocommerce": "<6.6|>=8.8,<8.8.5|>=8.9,<8.9.3", "wp-cli/wp-cli": ">=0.12,<2.5", - "wp-graphql/wp-graphql": "<=1.14.5", + "wp-coding-standards/wpcs": ">=0.14.1,<3.4.1", + "wp-graphql/wp-graphql": "<=2.6", "wp-premium/gravityforms": "<2.4.21", "wpanel/wpanel4-cms": "<=4.3.1", "wpcloud/wp-stateless": "<3.2", @@ -4757,12 +5036,12 @@ "xpressengine/xpressengine": "<3.0.15", "yab/quarx": "<2.4.5", "yansongda/pay": "<=3.7.19", - "yeswiki/yeswiki": "<=4.6", + "yeswiki/yeswiki": "<4.6.6", "yetiforce/yetiforce-crm": "<6.5", "yidashi/yii2cmf": "<=2", "yii2mod/yii2-cms": "<1.9.2", "yiisoft/yii": "<1.1.31", - "yiisoft/yii2": "<2.0.52", + "yiisoft/yii2": "<2.0.55", "yiisoft/yii2-authclient": "<2.2.15", "yiisoft/yii2-bootstrap": "<2.0.4", "yiisoft/yii2-dev": "<=2.0.45", @@ -4852,7 +5131,7 @@ "type": "tidelift" } ], - "time": "2026-04-28T23:21:55+00:00" + "time": "2026-08-01T00:01:24+00:00" }, { "name": "sebastian/cli-parser", @@ -5877,16 +6156,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.13.5", + "version": "3.13.6", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4" + "reference": "4c378e1a528ea066890fc2397cbdd2f94eb2fc91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0ca86845ce43291e8f5692c7356fccf3bcf02bf4", - "reference": "0ca86845ce43291e8f5692c7356fccf3bcf02bf4", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/4c378e1a528ea066890fc2397cbdd2f94eb2fc91", + "reference": "4c378e1a528ea066890fc2397cbdd2f94eb2fc91", "shasum": "" }, "require": { @@ -5952,7 +6231,7 @@ "type": "thanks_dev" } ], - "time": "2025-11-04T16:30:35+00:00" + "time": "2026-08-06T00:17:32+00:00" }, { "name": "symfony/config", @@ -6035,16 +6314,16 @@ }, { "name": "symfony/console", - "version": "v6.4.36", + "version": "v6.4.43", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9f481cfb580db8bcecc9b2d4c63f3e13df022ad5" + "reference": "3b643aa587acbc42f967a429af088a56ed8f046d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9f481cfb580db8bcecc9b2d4c63f3e13df022ad5", - "reference": "9f481cfb580db8bcecc9b2d4c63f3e13df022ad5", + "url": "https://api.github.com/repos/symfony/console/zipball/3b643aa587acbc42f967a429af088a56ed8f046d", + "reference": "3b643aa587acbc42f967a429af088a56ed8f046d", "shasum": "" }, "require": { @@ -6109,7 +6388,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.36" + "source": "https://github.com/symfony/console/tree/v6.4.43" }, "funding": [ { @@ -6129,7 +6408,7 @@ "type": "tidelift" } ], - "time": "2026-03-27T15:30:51+00:00" + "time": "2026-07-26T14:44:19+00:00" }, { "name": "symfony/dependency-injection", @@ -6218,16 +6497,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.7.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", - "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", "shasum": "" }, "require": { @@ -6265,7 +6544,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.1" }, "funding": [ { @@ -6285,20 +6564,20 @@ "type": "tidelift" } ], - "time": "2026-04-13T15:52:40+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.36", + "version": "v6.4.43", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "fc828863e26ceec86e2513b5e46aa0b149d76b69" + "reference": "ac405d324c10ebbbde6a6e58379bf81db10f1dbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/fc828863e26ceec86e2513b5e46aa0b149d76b69", - "reference": "fc828863e26ceec86e2513b5e46aa0b149d76b69", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ac405d324c10ebbbde6a6e58379bf81db10f1dbf", + "reference": "ac405d324c10ebbbde6a6e58379bf81db10f1dbf", "shasum": "" }, "require": { @@ -6349,7 +6628,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.36" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.43" }, "funding": [ { @@ -6369,20 +6648,20 @@ "type": "tidelift" } ], - "time": "2026-03-30T11:18:01+00:00" + "time": "2026-07-21T14:00:19+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", "shasum": "" }, "require": { @@ -6396,7 +6675,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -6429,7 +6708,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.7.1" }, "funding": [ { @@ -6440,25 +6719,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-06-05T06:23:12+00:00" }, { "name": "symfony/filesystem", - "version": "v6.4.34", + "version": "v6.4.43", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "01ffe0411b842f93c571e5c391f289c3fdd498c3" + "reference": "9ff03da12d67649fbd1f34ca95951554624d0a16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/01ffe0411b842f93c571e5c391f289c3fdd498c3", - "reference": "01ffe0411b842f93c571e5c391f289c3fdd498c3", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/9ff03da12d67649fbd1f34ca95951554624d0a16", + "reference": "9ff03da12d67649fbd1f34ca95951554624d0a16", "shasum": "" }, "require": { @@ -6495,7 +6778,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.34" + "source": "https://github.com/symfony/filesystem/tree/v6.4.43" }, "funding": [ { @@ -6515,20 +6798,20 @@ "type": "tidelift" } ], - "time": "2026-02-24T17:51:06+00:00" + "time": "2026-06-27T10:13:35+00:00" }, { "name": "symfony/finder", - "version": "v6.4.34", + "version": "v6.4.42", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "9590e86be1d1c57bfbb16d0dd040345378c20896" + "reference": "0b73dac42493acbadbba644207a715b254e9b029" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9590e86be1d1c57bfbb16d0dd040345378c20896", - "reference": "9590e86be1d1c57bfbb16d0dd040345378c20896", + "url": "https://api.github.com/repos/symfony/finder/zipball/0b73dac42493acbadbba644207a715b254e9b029", + "reference": "0b73dac42493acbadbba644207a715b254e9b029", "shasum": "" }, "require": { @@ -6563,7 +6846,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.34" + "source": "https://github.com/symfony/finder/tree/v6.4.42" }, "funding": [ { @@ -6583,7 +6866,7 @@ "type": "tidelift" } ], - "time": "2026-01-28T15:16:37+00:00" + "time": "2026-06-26T15:18:24+00:00" }, { "name": "symfony/polyfill-ctype", @@ -6670,16 +6953,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.37.0", + "version": "v1.41.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e" + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/4864388bfbd3001ce88e234fab652acd91fdc57e", - "reference": "4864388bfbd3001ce88e234fab652acd91fdc57e", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", + "reference": "bb899c1db0aa8127dc3afe8cda4a67eb24915f8d", "shasum": "" }, "require": { @@ -6728,7 +7011,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.41.0" }, "funding": [ { @@ -6748,20 +7031,20 @@ "type": "tidelift" } ], - "time": "2026-04-26T13:13:48+00:00" + "time": "2026-07-28T08:25:59+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.37.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { @@ -6813,7 +7096,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { @@ -6833,20 +7116,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.38.1", + "version": "v1.38.2", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92" + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92", - "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", "shasum": "" }, "require": { @@ -6898,7 +7181,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.1" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" }, "funding": [ { @@ -6918,20 +7201,20 @@ "type": "tidelift" } ], - "time": "2026-05-26T12:51:13+00:00" + "time": "2026-05-27T06:59:30+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.37.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", - "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", "shasum": "" }, "require": { @@ -6978,7 +7261,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.37.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" }, "funding": [ { @@ -6998,20 +7281,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-05-26T12:45:58+00:00" }, { "name": "symfony/process", - "version": "v6.4.33", + "version": "v6.4.41", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "c46e854e79b52d07666e43924a20cb6dc546644e" + "reference": "c8fc09bdfe9fde9aaa89b415a4477feaccec16a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c46e854e79b52d07666e43924a20cb6dc546644e", - "reference": "c46e854e79b52d07666e43924a20cb6dc546644e", + "url": "https://api.github.com/repos/symfony/process/zipball/c8fc09bdfe9fde9aaa89b415a4477feaccec16a7", + "reference": "c8fc09bdfe9fde9aaa89b415a4477feaccec16a7", "shasum": "" }, "require": { @@ -7043,7 +7326,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.33" + "source": "https://github.com/symfony/process/tree/v6.4.41" }, "funding": [ { @@ -7063,20 +7346,20 @@ "type": "tidelift" } ], - "time": "2026-01-23T16:02:12+00:00" + "time": "2026-05-23T13:47:21+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.1", + "version": "v3.7.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", "shasum": "" }, "require": { @@ -7094,7 +7377,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -7130,7 +7413,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.1" }, "funding": [ { @@ -7150,26 +7433,27 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2026-06-16T09:55:08+00:00" }, { "name": "symfony/string", - "version": "v6.4.34", + "version": "v7.4.15", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "2adaf4106f2ef4c67271971bde6d3fe0a6936432" + "reference": "e394af32256bf9e7bf80849d95e589167c10097b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/2adaf4106f2ef4c67271971bde6d3fe0a6936432", - "reference": "2adaf4106f2ef4c67271971bde6d3fe0a6936432", + "url": "https://api.github.com/repos/symfony/string/zipball/e394af32256bf9e7bf80849d95e589167c10097b", + "reference": "e394af32256bf9e7bf80849d95e589167c10097b", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-grapheme": "~1.33", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0" }, @@ -7177,10 +7461,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/intl": "^6.2|^7.0", + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0|^7.0" + "symfony/var-exporter": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { @@ -7219,7 +7504,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.34" + "source": "https://github.com/symfony/string/tree/v7.4.15" }, "funding": [ { @@ -7239,7 +7524,7 @@ "type": "tidelift" } ], - "time": "2026-02-08T20:44:54+00:00" + "time": "2026-07-28T07:33:02+00:00" }, { "name": "symfony/var-exporter", @@ -7707,9 +7992,9 @@ "platform": { "php": "^8.3" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/css/header-override.css b/css/header-override.css index c9828274..5d655840 100644 --- a/css/header-override.css +++ b/css/header-override.css @@ -1,6 +1,6 @@ /** * SPDX-FileCopyrightText: 2024 LaunchPad Contributors - * SPDX-License-Identifier: AGPL-3.0-or-later + * SPDX-License-Identifier: EUPL-1.2 * * NUCLEAR OPTION: Override ALL theme CSS for header styling * This file is loaded LAST to ensure it overrides nldesign theme diff --git a/css/launchpad.css b/css/launchpad.css index 7f37fc88..cd0d6de8 100644 --- a/css/launchpad.css +++ b/css/launchpad.css @@ -1,6 +1,6 @@ /** * SPDX-FileCopyrightText: 2024 LaunchPad Contributors - * SPDX-License-Identifier: AGPL-3.0-or-later + * SPDX-License-Identifier: EUPL-1.2 * * --launchpad-cell-height is set by `useGridManager.syncCellHeightCssVar()` at * grid-init time from the JS `CELL_HEIGHT` constant (REQ-GRID-012). The diff --git a/docs/GOVERNMENT-FEATURES.md b/docs/GOVERNMENT-FEATURES.md index 6c7bd4ef..78b4a512 100644 --- a/docs/GOVERNMENT-FEATURES.md +++ b/docs/GOVERNMENT-FEATURES.md @@ -5,7 +5,7 @@ **Product:** LaunchPad **Categorie:** Dashboard & informatievoorziening -**Licentie:** AGPL (vrije open source) +**Licentie:** EUPL-1.2 (vrije open source) **Leverancier:** Conduction B.V. **Platform:** Nextcloud (self-hosted / on-premise / cloud) @@ -57,7 +57,7 @@ | # | Eis | Status | Toelichting | |---|-----|--------|-------------| | T-01 | On-premise / self-hosted | Beschikbaar | Nextcloud-app | -| T-02 | Open source | Beschikbaar | AGPL, GitHub | +| T-02 | Open source | Beschikbaar | EUPL-1.2, GitHub | | T-03 | PHP 8.1+ | Beschikbaar | Moderne PHP | | T-04 | Nextcloud 28-33 compatibel | Beschikbaar | Brede versie-ondersteuning | | T-05 | Geen externe dependencies | Beschikbaar | Alleen Nextcloud vereist | diff --git a/docs/architecture.md b/docs/architecture.md index bc9ba3bd..b08ffab0 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -25,8 +25,9 @@ persists everything in its own tables via Doctrine mappers. │ components/WidgetRenderer — legacy-widget bridge │ │ components/WidgetPicker — "add widget" modal │ │ components/WidgetWrapper — per-tile chrome │ -│ components/TileCard / TileEditor / WidgetStyleEditor │ +│ components/TileCard / WidgetStyleEditor │ │ components/admin/AdminSettings — admin console │ +│ modals/ + dialogs/ — every NcModal / NcDialog surface │ └──────────────────────────┬──────────────────────────────────┘ │ OCS JSON via @nextcloud/axios ┌──────────────────────────▼──────────────────────────────────┐ diff --git a/docs/features.json b/docs/features.json index b0449913..5ceee6bf 100644 --- a/docs/features.json +++ b/docs/features.json @@ -1,398 +1,248 @@ [ - { - "slug": "activity-feed-integration", - "title": "Activity Feed Integration", - "summary": "Surface LaunchPad events in Nextcloud's standard Activity feed so every action on a dashboard — creation, editing, publication, sharing, commenting, locking, and role changes — is visible to the relevant users in their NC notifications and activity stream. This capability defines the NC Activity extension class, all event-type constants, audience-targeting rules, debounce logic, subject/message templates, icon conventions, the cross-capability emission contract, and the unit-test contract. Actual `publishActivity()` call-sites are delegated to the sibling capability that owns each action.", - "docsUrl": "openspec/specs/activity-feed-integration/spec.md" - }, - { - "slug": "admin-roles", - "title": "Admin Roles", - "summary": "Admin Roles provides a built-in role system scoped entirely within LaunchPad. Organization administrators can delegate dashboard management, widget installation, metadata field configuration, and other LaunchPad operations to trusted users without granting full Nextcloud system administration rights. Three roles (Dashboard Admin, Dashboard Editor, Dashboard Viewer) map to real organizational needs, and role assignments persist in a new table with support for both individual user and group-based delegation. Effective role resolution ensures the highest privilege wins when a user has multiple group memberships.", - "docsUrl": "openspec/specs/admin-roles/spec.md" - }, - { - "slug": "admin-settings", - "title": "Admin Settings", - "summary": "Admin settings provide Nextcloud administrators with global configuration options for the LaunchPad app. These settings control system-wide behavior such as whether users can create their own dashboards, how many dashboards they can have, default permission levels for new dashboards, and default grid configuration. Settings are stored as key-value pairs in a dedicated database table and are applied as defaults or constraints across the entire LaunchPad installation.", - "docsUrl": "openspec/specs/admin-settings/spec.md" - }, { "slug": "admin-templates", - "title": "Admin Templates", - "summary": "Admin templates allow Nextcloud administrators to create pre-configured dashboards that are automatically distributed to users based on group membership. When a user opens LaunchPad for the first time (or when a new template targets their group), the system creates a personal copy of the matching template. This copy is an independent dashboard that the user can modify within the limits of the inherited permission level. Templates enable organizations to provide standardized dashboard layouts with compulsory widgets while still allowing user customization where appropriate.", - "docsUrl": "openspec/specs/admin-templates/spec.md" + "title": "Admin templates", + "summary": "You push a curated homepage to a group in minutes.", + "status": "stable", + "docsUrl": "openspec/specs/admin-templates/spec.md", + "title_nl": "Beheersjablonen", + "summary_nl": "Je rolt een ingerichte startpagina in minuten uit naar een groep." }, { - "slug": "background-job-feed-refresh", - "title": "background-job-feed-refresh", - "summary": "Keeps news-widget feeds fresh by running a scheduled background job that fetches, parses, and caches RSS 2.0 and Atom 1.0 feeds referenced by dashboard placements. It deduplicates feed URLs into a shared cache table, uses HTTP conditional requests and per-feed failure isolation to stay efficient and resilient, enforces a host allow-list and concurrency lock, and exposes an admin endpoint to trigger an immediate refresh.", - "docsUrl": "openspec/specs/background-job-feed-refresh/spec.md" - }, - { - "slug": "calendar-widget", - "title": "Calendar Widget", - "summary": "The calendar widget is a built-in LaunchPad widget type that renders aggregated events from internal Nextcloud calendars and external ICS feeds in a single dashboard tile. Users can switch between three view modes (month, week, agenda), restrict the look-ahead window for agenda mode, and color events by their source calendar. External ICS feeds are fetched server-side with caching, an HTTPS-only / SSRF-safe guard, and an optional admin allow-list of permitted hostnames, so that adding a public calendar link to a dashboard does not expose the Nextcloud instance to internal-network probing or runaway expansion.", - "docsUrl": "openspec/specs/calendar-widget/spec.md" + "slug": "permissions", + "title": "Permission tiers", + "summary": "People personalise their homepage without touching the locked widgets.", + "status": "stable", + "docsUrl": "openspec/specs/permissions/spec.md", + "title_nl": "Rechtenniveaus", + "summary_nl": "Mensen passen hun startpagina aan zonder de vergrendelde widgets te raken." }, { - "slug": "cli-commands", - "title": "CLI Commands Suite", - "summary": "The CLI commands suite establishes a coherent, standardized operator interface for LaunchPad management tasks. It defines the `launchpad:` namespace prefix, consistent global flags across all commands, and introduces new operational helpers for dashboard inspection, sharing debugging, feed token management, and internationalization. The capability ensures scriptability, auditability, and discoverability of all CLI operations.", - "docsUrl": "openspec/specs/cli-commands/spec.md" + "slug": "admin-roles", + "title": "Role delegation", + "summary": "You hand a colleague dashboard admin without full Nextcloud rights.", + "status": "stable", + "docsUrl": "openspec/specs/admin-roles/spec.md", + "title_nl": "Roldelegatie", + "summary_nl": "Je geeft een collega dashboardbeheer zonder volledige Nextcloud-rechten." }, { "slug": "conditional-visibility", - "title": "Conditional Visibility", - "summary": "Conditional visibility allows widget placements to be shown or hidden based on dynamic rules. This enables dashboards that adapt to the user's context -- for example, showing a \"Team Updates\" widget only during business hours, displaying a \"Holiday Schedule\" widget only in December, or restricting certain widgets to specific user groups. Rules are evaluated at render time and can be inclusive (show when matched) or exclusive (hide when matched). Include rules use OR logic (at least one must match); exclude rules use AND logic (any match hides the widget).", - "docsUrl": "openspec/specs/conditional-visibility/spec.md" - }, - { - "slug": "confluence-html-import", - "title": "Confluence HTML Export Importer", - "summary": "Organisations migrating from Atlassian Confluence (or supplementing it with LaunchPad) need a one-shot bulk import that converts existing Confluence page hierarchies into LaunchPad dashboards. Manual recreation of hundreds of pages is impractical. This capability lets a Nextcloud admin upload a Confluence \"HTML Export\" archive and automatically generate LaunchPad dashboards with the page content preserved, the page tree mirrored via the `dashboard-tree` capability, and Confluence Storage Format macros expanded into safe HTML.", - "docsUrl": "openspec/specs/confluence-html-import/spec.md" - }, - { - "slug": "container-widget", - "title": "Container Widget", - "summary": "The container widget hosts a sub-grid of child widget placements inside a single outer-grid cell. Authors compose dashboards out of logical sections (a heading + four KPI tiles, a \"tabs\" surface, a card with grouped content) without losing the move-as-one-unit drag behaviour of a top-level placement. Children are stored as nested `placements: WidgetPlacement[]` in the container's `content` blob and dispatched through the same widget registry that drives the top-level grid, so any registered widget type — including another container — can live inside one. Server-side validation caps recursion at three nested container levels (REQ-CONT-006) to prevent runaway depth.", - "docsUrl": "openspec/specs/container-widget/spec.md" - }, - { - "slug": "dashboard-bulk-operations", - "title": "Dashboard Bulk Operations", - "summary": "Dashboard bulk operations expose four batch admin endpoints for large-scale management of LaunchPad dashboards: bulk delete, bulk re-parent, bulk publication-status update, and bulk re-index. The endpoints provide all-or-nothing permission pre-checks, per-dashboard atomic mutations with continue-on-error semantics, dry-run preview support, and a single audit Activity event per request. The design closes the misclick gap of the source implementation (which defaulted `cascade=true` on bulk delete) by requiring an explicit opt-in for recursive deletion, and pins a 500-dashboard per-request cap that is admin-tunable via the `bulk_operation_max_per_request` app config key.", - "docsUrl": "openspec/specs/dashboard-bulk-operations/spec.md" - }, - { - "slug": "dashboard-cascade-events", - "title": "Dashboard Cascade Events", - "summary": "When a LaunchPad dashboard is deleted, all dependent data (widget placements, comments, reactions, locks, versions, public shares, metadata values, translations, view analytics, child-tree dashboards) MUST be automatically removed. When a Nextcloud user or group is deleted, their associated dashboards and downstream records MUST likewise be cleaned up. This capability defines the event, the listener registry, failure isolation, idempotency, and cascade stats reporting.", - "docsUrl": "openspec/specs/dashboard-cascade-events/spec.md" - }, - { - "slug": "dashboard-comments", - "title": "Dashboard Comments", - "summary": "Dashboards are shared workspaces within teams and organizations. Dashboard Comments adds a threaded discussion surface so users can ask \"why is this widget red?\" or \"we should change this metric next sprint\" directly on the dashboard they are looking at. Comments are persisted via Nextcloud's native `ICommentsManager` infrastructure (the same backend used by the file comments and Talk integrations) so administrators get unified comment storage, notifications, and audit trails — and so LaunchPad does not introduce a redundant comment table.", - "docsUrl": "openspec/specs/dashboard-comments/spec.md" - }, - { - "slug": "dashboard-export-import", - "title": "Dashboard Export & Import", - "summary": "Dashboard export and import allow LaunchPad administrators to create versioned snapshots of dashboard configurations, widgets, metadata fields, and associated assets. Snapshots are portable across Nextcloud instances, enabling backup, disaster recovery, template authoring, and cross-instance sharing. This capability defines a standardised ZIP container format (`launchpad-export-v1.zip`), collision handling semantics, and API/CLI endpoints for end-to-end export-import workflows. Downstream capabilities such as `confluence-html-import` consume the same ZIP shape, so the manifest schema (`schemaVersion: 1`) is treated as a stable contract.", - "docsUrl": "openspec/specs/dashboard-export-import/spec.md" - }, - { - "slug": "dashboard-icons", - "title": "Dashboard Icons", - "summary": "LaunchPad dashboards (and the dashboard-list items in the switcher sidebar and admin UI) display an icon next to their name. This capability owns the icon vocabulary: a small curated registry of named built-in icons that live in the frontend bundle, plus the lookup/render functions consumers use, plus the convention for storing per-dashboard icons in a single column that may also hold an uploaded resource URL.", - "docsUrl": "openspec/specs/dashboard-icons/spec.md" + "title": "Conditional visibility", + "summary": "You show finance cards to finance and banners only this week.", + "status": "stable", + "docsUrl": "openspec/specs/conditional-visibility/spec.md", + "title_nl": "Voorwaardelijke zichtbaarheid", + "summary_nl": "Je toont finance-kaarten aan finance en banners alleen deze week." }, { "slug": "dashboard-kiosk-mode", - "title": "dashboard-kiosk-mode", - "summary": "Turns LaunchPad dashboards into unattended signage by rendering them chrome-less and full-viewport via a `kiosk=1` flag or a public playlist token. Users can build named playlists that rotate through multiple dashboards with per-entry dwell times, while kiosk surfaces refresh widget data in place and degrade gracefully on failure with skip-and-retry, last-known-content retention, neutral placeholders, and a watchdog to recover from stalls.", - "docsUrl": "openspec/specs/dashboard-kiosk-mode/spec.md" - }, - { - "slug": "dashboard-locking", - "title": "Dashboard Locking", - "summary": "Dashboard locking provides a concurrent-edit guard for dashboards. When two users open the same dashboard's edit view, the system MUST prevent the second user from editing until the first releases the lock. The mechanism uses a time-based lease (default 15 minutes) with client-driven heartbeat renewal to tolerate transient network outages and browser crashes without manual intervention.", - "docsUrl": "openspec/specs/dashboard-locking/spec.md" - }, - { - "slug": "dashboard-metadata-fields", - "title": "Dashboard Metadata Fields", - "summary": "Dashboard Metadata Fields allow administrators to define custom, queryable attributes that can be attached to every dashboard in a LaunchPad instance. Once an administrator defines a global registry of field definitions (e.g., \"department\", \"project stage\", \"audience\"), end users populate values for those fields on their dashboards. The field values are then queryable for filtering dashboards in search, widget configuration, and API calls. This capability standardizes what would otherwise be ad-hoc naming conventions and enables the discovery and organization of dashboards at scale.", - "docsUrl": "openspec/specs/dashboard-metadata-fields/spec.md" - }, - { - "slug": "dashboard-public-share", - "title": "dashboard-public-share", - "summary": "Lets dashboard owners publish read-only public links to their dashboards, optionally protected by a password and an expiry date. Anonymous visitors render the dashboard through a unique share token while the system enforces read-only access, soft-revocation, brute-force throttling, debounced view counting, and a service-account read path for GroupFolder-backed content.", - "docsUrl": "openspec/specs/dashboard-public-share/spec.md" + "title": "Kiosk and signage mode", + "summary": "You rotate dashboards on a lobby screen unattended.", + "status": "stable", + "docsUrl": "openspec/specs/dashboard-kiosk-mode/spec.md", + "title_nl": "Kiosk- en narrowcastmodus", + "summary_nl": "Je laat dashboards onbemand rouleren op een lobbyscherm." }, { "slug": "dashboard-quota-limits", - "title": "dashboard-quota-limits", - "summary": "Numeric admin-governance quotas for LaunchPad: maximum personal dashboards per user and maximum widget placements per dashboard. Both default to `0` (unlimited). Enforcement is server-side and fail-closed at a single `QuotaService` choke point on every user-initiated creation path, returning HTTP 409 with a structured body; admin provisioning is exempt; lowering a limit grandfathers existing data (never deletes/hides); and the dashboards list response carries an additive `quota` envelope so the UI can disable creation affordances at the limit while the server check stays authoritative.", - "docsUrl": "openspec/specs/dashboard-quota-limits/spec.md" - }, - { - "slug": "dashboard-reactions", - "title": "Dashboard Reactions", - "summary": "Dashboard reactions enable lightweight social feedback via emoji on LaunchPad dashboards. Users can react with a configurable whitelist of emojis to mark dashboards as useful, appreciated, or funny, without requiring full-featured comments. Reactions are aggregated by emoji and visible to all viewers. An administrator can enable/disable reactions globally and per-dashboard, and can curate the allowed emoji list.", - "docsUrl": "openspec/specs/dashboard-reactions/spec.md" - }, - { - "slug": "dashboard-rss-feeds", - "title": "Dashboard RSS Feeds", - "summary": "Expose a user's accessible dashboards as an RSS 2.0 / Atom feed accessible without Nextcloud browser authentication via a per-user secret token. Each user may opt-in by requesting their feed token; the feed is filtered by the token-owner's dashboard ACLs (permissions) so private content remains private. The feed enables integration with RSS readers, monitoring tools, and third-party systems without requiring full Nextcloud login.", - "docsUrl": "openspec/specs/dashboard-rss-feeds/spec.md" + "title": "Dashboard quotas", + "summary": "You cap dashboards per user and keep the instance tidy.", + "status": "stable", + "docsUrl": "openspec/specs/dashboard-quota-limits/spec.md", + "title_nl": "Dashboardquota", + "summary_nl": "Je begrenst dashboards per gebruiker en houdt de omgeving netjes." }, { "slug": "dashboard-switcher", - "title": "Dashboard Switcher", - "summary": "The dashboard switcher is a left-edge slide-in sidebar that lets a user see every dashboard visible to them and switch between them with a single click. Dashboards are grouped into three labelled sections by source (primary group, default group, personal). The sidebar also surfaces personal-dashboard creation and deletion when allowed.", - "docsUrl": "openspec/specs/dashboard-switcher/spec.md" - }, - { - "slug": "dashboard-versioning", - "title": "Dashboard Versioning", - "summary": "Enable version history and one-click restoration for LaunchPad dashboards. The feature delegates storage strategy to the underlying content backend: dashboards stored in Nextcloud Files (via the `groupfolder` backend) use NC's native file versioning; dashboards in the database backend use a dedicated `oc_launchpad_dash_versions` table. All APIs are backend-agnostic.", - "docsUrl": "openspec/specs/dashboard-versioning/spec.md" - }, - { - "slug": "dashboard-view-analytics", - "title": "Dashboard View Analytics", - "summary": "Aggregate, privacy-preserving view counts per dashboard so LaunchPad administrators can understand which dashboards are actually being used. Counts are bucketed by UTC day and stored in a single aggregate table (`oc_launchpad_dashboard_views`). Unique-viewer deduplication uses a daily-rotating salted SHA-256 hash kept exclusively in the Nextcloud cache layer; no per-user-per-event rows are persisted, and cross-day re-identification from the analytics database alone is computationally infeasible. Admins query top dashboards, per-dashboard daily breakdowns, instance-wide totals, and CSV exports through admin-only endpoints. A daily background job purges rows older than the configured retention window (default 365 days, clamped to `[30, 3650]`).", - "docsUrl": "openspec/specs/dashboard-view-analytics/spec.md" + "title": "Multiple dashboards", + "summary": "You build a homepage per role and switch in one click.", + "status": "stable", + "docsUrl": "openspec/specs/dashboard-switcher/spec.md", + "title_nl": "Meerdere dashboards", + "summary_nl": "Je bouwt een startpagina per rol en wisselt met een klik." }, { - "slug": "dashboards", - "title": "Dashboards", - "summary": "Dashboards are the core organizational unit in LaunchPad. Each user can create and manage multiple personal dashboards, each acting as a container for widget placements, tiles, and layout configuration. Dashboards define the grid structure, permission level, and active state. Only one dashboard can be active per user at a time, serving as their landing page when they open Nextcloud. Dashboards can also be of type `admin_template`, managed by administrators for distribution to users.", - "docsUrl": "openspec/specs/dashboards/spec.md" + "slug": "grid-layout", + "title": "Drag-and-drop grid", + "summary": "You drag, drop, and resize cards until the layout fits.", + "status": "stable", + "docsUrl": "openspec/specs/grid-layout/spec.md", + "title_nl": "Sleep-en-neerzet raster", + "summary_nl": "Je sleept, plaatst en schaalt kaarten tot de indeling klopt." }, { - "slug": "demo-data-showcases", - "title": "Demo Data Showcases", - "summary": "The `demo-data-showcases` capability provides administrators with one-click installation of pre-built, fully populated example dashboards that illustrate different organizational use cases. Showcases are bundled as ZIP archives containing a machine-readable `export.json` manifest plus per-locale page JSON files and media assets, loaded from disk on demand, and installed as `group_shared` dashboards visible to all users (via REQ-DASH-012 default-group sentinel). The capability includes widget type validation, graceful skip-on-missing for unknown widgets, NL-only localization in v1, and idempotent installation via API and CLI commands.", - "docsUrl": "openspec/specs/demo-data-showcases/spec.md" + "slug": "nc-dashboard-widget-proxy", + "title": "Nextcloud widgets", + "summary": "You drop in any Files, Calendar, or Talk widget you already use.", + "status": "stable", + "docsUrl": "openspec/specs/nc-dashboard-widget-proxy/spec.md", + "title_nl": "Nextcloud-widgets", + "summary_nl": "Je plaatst elke Bestanden-, Agenda- of Talk-widget die je al gebruikt." }, { - "slug": "divider-widget", - "title": "Divider Widget", - "summary": "The divider widget is a lightweight, configurable visual separator for LaunchPad dashboards. It enables dashboard creators to break up widget sections into logical groups using minimal UI — a horizontal line, whitespace spacer, or centered heading with dividing lines — all rendered client-side with full theme awareness and print support. This capability adds no backend endpoints or data storage; all configuration is stored in the placement's `widgetContent JSON` blob and rendered in-browser.", - "docsUrl": "openspec/specs/divider-widget/spec.md" + "slug": "tiles", + "title": "Shortcut tiles", + "summary": "You pin your key tools with an icon, colour, and link.", + "status": "stable", + "docsUrl": "openspec/specs/tiles/spec.md", + "title_nl": "Snelkoppelingstegels", + "summary_nl": "Je pint je belangrijkste tools met een icoon, kleur en link." }, { - "slug": "files-widget", - "title": "Files Widget", - "summary": "The files widget is a built-in LaunchPad widget type that lets dashboard authors embed an inline Nextcloud Files browser directly on a dashboard. The widget reads the configured folder live at render time, applies view-time ACL so each viewer sees only files they may read, supports folder navigation via a breadcrumb, deep-links file clicks into the standard Files application, and exposes optional upload and delete actions gated by both placement-level toggles and per-viewer permission. The capability is one widget type, one renderer, one sub-form, one registry entry, and three HTTP endpoints (contents listing, multi-file upload, single-file delete) — small enough to ship and evolve independently while anchoring the future \"shared workspace folder\" experience that other widgets will build on top of.", - "docsUrl": "openspec/specs/files-widget/spec.md" + "slug": "dashboard-public-share", + "title": "Public share links", + "summary": "You share a read-only dashboard with a single link.", + "status": "stable", + "docsUrl": "openspec/specs/dashboard-public-share/spec.md", + "title_nl": "Openbare deellinks", + "summary_nl": "Je deelt een alleen-lezen dashboard met een enkele link." }, { - "slug": "footer-customization", - "title": "Footer Customization", - "summary": "Footer Customization provides per-instance branding, legal disclaimers, and contact information rendered below the dashboard surface. Administrators configure global footer content (HTML or structured form), with optional per-dashboard overrides. The footer respects theme colors, supports multi-language variants, and prints correctly in PDF exports.", - "docsUrl": "openspec/specs/footer-customization/spec.md" + "slug": "dashboard-versioning", + "title": "Versioning and rollback", + "summary": "You roll a dashboard back to last week in one step.", + "status": "stable", + "docsUrl": "openspec/specs/dashboard-versioning/spec.md", + "title_nl": "Versiebeheer en terugdraaien", + "summary_nl": "Je draait een dashboard in een stap terug naar vorige week." }, { - "slug": "grid-layout", - "title": "Grid Layout", - "summary": "The grid layout system powers the drag-and-drop dashboard experience in LaunchPad. Built on GridStack 12.x, it provides a 12-column responsive grid that reflows at four explicit viewport breakpoints (1400/1100/768/480 px → 12/8/4/1 cols) where users can position, resize, and rearrange widget placements and tiles. The grid operates in two modes: view mode (static, no interaction) and edit mode (drag-and-drop enabled). Position changes are emitted via Vue events and persisted via the API by the parent component.", - "docsUrl": "openspec/specs/grid-layout/spec.md" + "slug": "dashboard-export-import", + "title": "Export and import", + "summary": "You move a dashboard between environments from the UI or the command line.", + "status": "stable", + "docsUrl": "openspec/specs/dashboard-export-import/spec.md", + "title_nl": "Exporteren en importeren", + "summary_nl": "Je verplaatst een dashboard tussen omgevingen via de interface of de opdrachtregel." + }, + { + "slug": "runtime-or-consumption", + "title": "Live business data", + "summary": "You pull live figures over GraphQL when OpenRegister is present.", + "status": "stable", + "docsUrl": "openspec/specs/runtime-or-consumption/spec.md", + "providedBy": "openregister", + "title_nl": "Live bedrijfsdata", + "summary_nl": "Je haalt live cijfers op via GraphQL wanneer OpenRegister aanwezig is." + }, + { + "slug": "kpi-cards", + "title": "KPI cards", + "summary": "You show counts and charts straight on the dashboard.", + "status": "stable", + "docsUrl": "openspec/specs/default-widget-bundle/spec.md", + "providedBy": "openregister", + "title_nl": "KPI-kaarten", + "summary_nl": "Je toont aantallen en grafieken direct op het dashboard." }, { - "slug": "groupfolder-storage-backend", - "title": "groupfolder-storage-backend", - "summary": "Abstracts dashboard content storage behind a unified read/write/delete interface so operators can choose between the default database backend and an optional Nextcloud GroupFolder backend. The GroupFolder backend stores dashboards as human-readable JSON files in an auto-created, admin-restricted folder, fails closed without silent fallback, and ships a one-time migration command — all transparently to existing API clients.", - "docsUrl": "openspec/specs/groupfolder-storage-backend/spec.md" + "slug": "confluence-html-import", + "title": "Confluence import", + "summary": "You bring a Confluence page in as dashboard content.", + "status": "stable", + "docsUrl": "openspec/specs/confluence-html-import/spec.md", + "title_nl": "Confluence-import", + "summary_nl": "Je haalt een Confluence-pagina binnen als dashboardinhoud." }, { - "slug": "header-widget", - "title": "Header Widget", - "summary": "The header widget is a built-in LaunchPad widget type that drops a full-width banner onto a dashboard with a configurable title, optional subtitle, optional background image (URL or NC file), an optional color overlay, and an optional call-to-action button. It replaces the legacy \"header row\" prototype with a first-class, typed widget that participates in the same registry, modal, and grid pipeline as every other built-in widget — no special-casing in the dashboard renderer.", - "docsUrl": "openspec/specs/header-widget/spec.md" + "slug": "prometheus-metrics", + "title": "Prometheus metrics", + "summary": "You scrape health and usage from a standard metrics endpoint.", + "status": "stable", + "docsUrl": "openspec/specs/prometheus-metrics/spec.md", + "title_nl": "Prometheus-metrieken", + "summary_nl": "Je leest gezondheid en gebruik uit via een standaard metrics-endpoint." }, { - "slug": "image-widget", - "title": "Image Widget", - "summary": "The image widget is a built-in LaunchPad widget type that lets dashboard authors place a single image — logo, screenshot, branding, or decorative imagery — onto a dashboard cell with proper `object-fit` control, broken-image fallback, optional click-through, and a first-class file-upload UX. It replaces the prior workarounds where users jammed `` tags into the markdown widget or pointed an iframe widget at an image URL.", - "docsUrl": "openspec/specs/image-widget/spec.md" + "slug": "orphaned-data-cleanup", + "title": "Data cleanup", + "summary": "A background job clears orphaned data on its own.", + "status": "stable", + "docsUrl": "openspec/specs/orphaned-data-cleanup/spec.md", + "title_nl": "Data-opschoning", + "summary_nl": "Een achtergrondtaak ruimt verweesde data vanzelf op." }, { - "slug": "infrastructure-helpers", - "title": "Infrastructure Helpers", - "summary": "The `infrastructure-helpers` capability collects small, pure (or nearly-pure) utility classes that are reused across multiple capability boundaries. These are not domain logic — they are primitives: string transformation, lookup, parameter extraction. Each helper has a single narrow contract, no persistence, and is invoked from multiple capability code paths. Grouping them here keeps each individual capability spec focused on domain behaviour rather than utility internals.", - "docsUrl": "openspec/specs/infrastructure-helpers/spec.md" + "slug": "activity-feed-integration", + "title": "Activity feed", + "summary": "Core dashboard actions land in the Nextcloud activity stream.", + "status": "beta", + "docsUrl": "openspec/specs/activity-feed-integration/spec.md", + "title_nl": "Activiteitenoverzicht", + "summary_nl": "Kerndashboardacties komen in de Nextcloud-activiteitenstroom terecht." }, { - "slug": "initial-state-contract", - "title": "Initial State Contract", - "summary": "The `initial-state-contract` capability formalises the precise set of keys that PHP pushes via Nextcloud's `IInitialState::provideInitialState` for each Vue mount in LaunchPad, and the matching `provide()` calls each entry point emits to expose those keys to the rest of the component tree. Without this contract the keys drift silently — frontend reads a key the backend stopped sending, or vice versa, and the breakage only surfaces at runtime.", - "docsUrl": "openspec/specs/initial-state-contract/spec.md" + "slug": "nc-unified-search-integration", + "title": "Unified search", + "summary": "You find dashboards from the Nextcloud search bar.", + "status": "beta", + "docsUrl": "openspec/specs/nc-unified-search-integration/spec.md", + "title_nl": "Geintegreerd zoeken", + "summary_nl": "Je vindt dashboards via de Nextcloud-zoekbalk." }, { - "slug": "label-widget", - "title": "Label Widget", - "summary": "The label widget is a built-in LaunchPad widget type that lets dashboard authors drop a short, single-line, plain-text heading onto a dashboard to title a row of widgets or mark a zone. It is intentionally narrower than the `text` widget (which carries multi-line HTML content via `v-html`): the label widget renders content with Vue interpolation only, eliminating the XSS surface entirely, and ships heading-style defaults (`16px` bold centred) so a freshly added label looks correct without any styling input.", - "docsUrl": "openspec/specs/label-widget/spec.md" + "slug": "groupfolder-storage-backend", + "title": "GroupFolder storage (optional)", + "summary": "You optionally store dashboard content in a shared GroupFolder.", + "status": "beta", + "docsUrl": "openspec/specs/groupfolder-storage-backend/spec.md", + "title_nl": "GroupFolder-opslag (optioneel)", + "summary_nl": "Je bewaart dashboardinhoud optioneel in een gedeelde GroupFolder." }, { - "slug": "launchpad-adopt-or-abstractions", - "title": "launchpad-adopt-or-abstractions", - "summary": "Keeps LaunchPad installable and runnable without OpenRegister or OpenConnector while letting its widgets consume OR data when present. It mandates an architectural manifest, forbids install-time OR/OC dependencies, requires runtime feature-detection with documented empty states, locale and tenant-context stamping on OR fetches, a local-first dashboard permission model, and several code-hygiene rules (typed admin-setting keys, a named filename-pattern constant, and documented column-type constants).", - "docsUrl": "openspec/specs/launchpad-adopt-or-abstractions/spec.md" + "slug": "launchpad-spend-analytics-widget", + "title": "Spend analytics card", + "summary": "You preview spend from financeq and procest on a card.", + "status": "beta", + "docsUrl": "openspec/specs/launchpad-spend-analytics-widget/spec.md", + "providedBy": "openregister", + "title_nl": "Uitgavenanalyse-kaart", + "summary_nl": "Je bekijkt uitgaven uit financeq en procest op een kaart." }, { - "slug": "launchpad-ai-dashboard-assistant", - "title": "Spec: launchpad-ai-dashboard-assistant", - "summary": "Add an embedded AI assistant widget (`launchpad_ai_assistant`) that lets the dashboard viewer ask natural-language questions about their dashboard's data and receive a streamed reply — summarising open cases, surfacing consultation responses, explaining an aggregate trend. The widget is a **thin chat surface** that delegates inference to the openconnector-registered LLM source (Ollama + Qwen via `local-llm` per `reference_llphant-ollama-think-false`). launchpad MUST NOT carry its own LLM client SDK or call any inference endpoint directly.", - "docsUrl": "openspec/specs/launchpad-ai-dashboard-assistant/spec.md" + "slug": "launchpad-mobile-remote-access", + "title": "Mobile access", + "summary": "A responsive homepage that survives on a phone.", + "status": "soon", + "docsUrl": "openspec/specs/launchpad-mobile-remote-access/spec.md", + "title_nl": "Mobiele toegang", + "summary_nl": "Een responsieve startpagina die het op een telefoon volhoudt." }, { "slug": "launchpad-compliance-audit-panel", - "title": "Spec: launchpad-compliance-audit-panel", - "summary": "Surface the organisation's compliance posture on a launchpad dashboard through one widget (`launchpad_compliance_audit`). The widget reads audit-trail, retention, and compliance-evidence data at runtime via GraphQL — consuming OR's `audit-trail-immutable` and `archival-destruction-workflow` abstractions (ADR-022 table rows \"Audit trail\" + \"Archival + destruction workflow\") plus shillinq's Archiefwet retention rules and docudesk's compliance documents.", - "docsUrl": "openspec/specs/launchpad-compliance-audit-panel/spec.md" + "title": "Audit panel", + "summary": "A dedicated compliance and audit-trail view.", + "status": "soon", + "docsUrl": "openspec/specs/launchpad-compliance-audit-panel/spec.md", + "title_nl": "Auditpaneel", + "summary_nl": "Een eigen weergave voor compliance en het auditspoor." }, { "slug": "launchpad-enterprise-security-access", - "title": "Spec: launchpad-enterprise-security-access", - "summary": "Surface enterprise security and access posture on a launchpad dashboard through a read-only widget (`launchpad_security_access`). Three card surfaces compose the widget:", - "docsUrl": "openspec/specs/launchpad-enterprise-security-access/spec.md" + "title": "SSO and access posture", + "summary": "You surface SAML, TOTP, and WebAuthn status on the dashboard.", + "status": "soon", + "docsUrl": "openspec/specs/launchpad-enterprise-security-access/spec.md", + "title_nl": "SSO en toegangsstatus", + "summary_nl": "Je toont de status van SAML, TOTP en WebAuthn op het dashboard." }, { - "slug": "launchpad-file-access-widget", - "title": "Spec: launchpad-file-access-widget", - "summary": "Surface dossier documents (and arbitrary Nextcloud Files objects) on a launchpad dashboard via a single widget (`launchpad_file_access`). The widget is a **quick-access surface** distinct from the existing `files-widget` (which embeds a folder browser): this widget renders a curated short-list of files the viewer needs from the dashboard — typically the documents attached to a dossier object via OR's `object-interactions` integration (ADR-019 / ADR-022).", - "docsUrl": "openspec/specs/launchpad-file-access-widget/spec.md" + "slug": "launchpad-ai-dashboard-assistant", + "title": "AI assistant", + "summary": "You ask for the widget you need and have it placed for you.", + "status": "soon", + "docsUrl": "openspec/specs/launchpad-ai-dashboard-assistant/spec.md", + "title_nl": "AI-assistent", + "summary_nl": "Je vraagt om de widget die je nodig hebt en die wordt voor je geplaatst." }, { "slug": "launchpad-meeting-calendar-actions", - "title": "Spec: launchpad-meeting-calendar-actions", - "summary": "Surface meeting and agenda actions on a launchpad dashboard via the widget `launchpad_meeting_actions`. The widget composes data from two sources, presented as one timeline:", - "docsUrl": "openspec/specs/launchpad-meeting-calendar-actions/spec.md" - }, - { - "slug": "launchpad-mobile-remote-access", - "title": "Spec: launchpad-mobile-remote-access", - "summary": "Define the **manifest contract** for declaring widget mobile-readiness so the launchpad workspace can render a coherent mobile + remote experience. The existing `responsive-grid-breakpoints` spec already handles the grid-side breakpoint engine; this spec adds the **per-widget declaration surface** in the app manifest (per ADR-024) and the fallback behaviour when no widget on a dashboard declares itself mobile-ready.", - "docsUrl": "openspec/specs/launchpad-mobile-remote-access/spec.md" - }, - { - "slug": "launchpad-spend-analytics-widget", - "title": "Spec: launchpad-spend-analytics-widget", - "summary": "Surface procurement + financial spend analytics on a launchpad dashboard as a single widget (`launchpad_spend_analytics`). The widget consumes data live at render time via runtime GraphQL queries against financeq + procest (and, for evidence attachment, docudesk through OR's `object-interactions` integration registry per ADR-022). The widget MUST NOT add an install-time dependency on any sibling app — per `feedback_launchpad-no-or-dependency.md`, launchpad stays the always-available shell.", - "docsUrl": "openspec/specs/launchpad-spend-analytics-widget/spec.md" - }, - { - "slug": "legacy-widget-bridge", - "title": "Legacy Widget Bridge", - "summary": "LaunchPad's grid can render widgets from two eras of the Nextcloud widget API: modern widgets that implement `IAPIWidget` / `IAPIWidgetV2` (covered by the [widgets](../widgets/spec.md) capability), and legacy widgets that use the older callback-registration pattern by calling `window.OCA.Dashboard.register(appId, callback)` at bootstrap. This capability covers the client-side bridge that captures those legacy registrations so LaunchPad can mount them into the grid on demand.", - "docsUrl": "openspec/specs/legacy-widget-bridge/spec.md" - }, - { - "slug": "link-button-widget", - "title": "Link-Button Widget", - "summary": "The link-button widget is a built-in LaunchPad widget type that lets dashboard authors drop a styled, clickable tile onto a dashboard. The tile dispatches one of three explicit action types — open an external URL in a new tab, invoke a registered in-app workflow, or create a fresh document in the user's Files area. The capability formalises a typed `actionType` enum so the action set can grow safely (no fragile auto-detect-from-extension semantics like the earlier prototype), pairs the renderer with a singleton frontend registry of named internal actions, and pairs the createFile flow with a strictly-validated server endpoint that gates new files behind an admin-configurable extension allow-list.", - "docsUrl": "openspec/specs/link-button-widget/spec.md" - }, - { - "slug": "links-widget", - "title": "Links Widget", - "summary": "Provide a multi-column dashboard widget that renders a curated grid of link cards organised into named sections. Distinct from the single-button `link-button-widget` and the high-density `quicklinks-widget`, this widget is optimised for \"link directory\" layouts: each section carries a heading and an arbitrary number of links, each link carries a label, URL, optional icon, and optional description, and the renderer offers three layout modes (`card`, `inline`, `icon-only`). All configuration lives in the placement `widgetContent` JSON — there is no backend data endpoint and no migration. URL sanitisation runs at save time to defend against `javascript:`, `data:`, and `file://` XSS vectors.", - "docsUrl": "openspec/specs/links-widget/spec.md" - }, - { - "slug": "menu-widget", - "title": "Menu Widget", - "summary": "The menu widget is a built-in LaunchPad widget type that renders a hierarchical, in-page navigation tree distinct from the application sidebar. It supports up to three levels of nesting and three visual styles — `dropdown`, `megamenu`, and `tree` — so dashboard authors can publish curated link sets that fit the surrounding layout without writing custom Vue code.", - "docsUrl": "openspec/specs/menu-widget/spec.md" - }, - { - "slug": "navigation-editor-org", - "title": "Organization-wide Navigation Editor", - "summary": "The `navigation-editor-org` capability provides a robust, admin-curated, group-aware org-wide navigation tree distinct from the personal dashboard list. Where the existing `dashboard-switcher-sidebar` shows the dashboards a user owns or can access, this capability adds a second navigation surface — an admin-controlled tree of links and sections shared across the whole organisation. Useful for company resources, policy hubs, and tools panels.", - "docsUrl": "openspec/specs/navigation-editor-org/spec.md" - }, - { - "slug": "nc-dashboard-widget-proxy", - "title": "nc-dashboard-widget-proxy", - "summary": "Defines the user-facing surface of the Nextcloud Dashboard widget proxy (`nc-widget` placement type) — primarily the picker UX inside the unified Add Custom Widget modal. The renderer/contract for `nc-widget` placements themselves is owned by the `widgets` capability (REQ-WDG-018 onwards) and the bridge polling behaviour by `legacy-widget-bridge`. This spec narrows in on how end users discover and pick a Nextcloud-discovered widget when configuring an `nc-widget` placement.", - "docsUrl": "openspec/specs/nc-dashboard-widget-proxy/spec.md" - }, - { - "slug": "nc-unified-search-integration", - "title": "Nextcloud Unified Search Integration", - "summary": "Nextcloud's unified search (Ctrl+K / Cmd+K) provides a global discovery mechanism for content across all installed apps. LaunchPad dashboards, widgets, and metadata are exposed to this search via a registered `OCP\\Search\\IProvider` so users can discover and navigate to dashboards by name, description, widget content, or metadata field values from the global search bar without entering the app first.", - "docsUrl": "openspec/specs/nc-unified-search-integration/spec.md" - }, - { - "slug": "news-widget", - "title": "News Widget", - "summary": "The news widget aggregates RSS and Atom feed items from one or more configured sources and renders them on a LaunchPad dashboard. Per-placement configuration controls which feeds are included, the layout mode (list, grid, carousel), an optional item cap, presentation switches (thumbnails, summary, date format), and an optional metadata-based filter that suppresses the widget on dashboards whose metadata does not match. HTML sanitisation, host allow-listing, failure tolerance, and link-security defaults are all enforced server-side; the Vue renderer never re-sanitises and never performs upstream fetches.", - "docsUrl": "openspec/specs/news-widget/spec.md" - }, - { - "slug": "orphaned-data-cleanup", - "title": "Orphaned Data Cleanup", - "summary": "Provide administrators with a comprehensive, safe, and auditable mechanism to scan for and remove orphaned LaunchPad data: expired locks and tokens, widget assets from deleted dashboards, metadata-value rows with missing field definitions, placements with no dashboard, tokens for deleted users, role assignments for deleted users/groups, and translations for deleted dashboards. The capability MUST support dry-run (safe preview), per-category selectivity (scan vs. auto-purge), background automation (daily safe-categories job), and audit trails (activity events). A registry pattern enables adding new cleanup categories without editing central code.", - "docsUrl": "openspec/specs/orphaned-data-cleanup/spec.md" - }, - { - "slug": "people-widget", - "title": "People Widget", - "summary": "The `people-widget` capability registers a dashboard widget that displays a discoverable directory of Nextcloud users with customizable layout (card/grid/list), profile field visibility control, group filtering, and birthday tracking. The widget integrates with Nextcloud's Dashboard Widget API via `OCP\\Dashboard\\IManager`, stores configuration in the widget placement's JSON config, and provides a paginated API endpoint for user lookup. Results expose each user's profile fields as returned by `OCP\\Accounts\\IAccountManager`; scope-based visibility filtering is a planned follow-up (see REQ-PPL-004).", - "docsUrl": "openspec/specs/people-widget/spec.md" - }, - { - "slug": "permissions", - "title": "Permission Levels", - "summary": "Permission levels control what users can do with their dashboards. When an admin template is distributed to users, the template's permission level is inherited by the user's personal copy, restricting their editing capabilities. This system allows administrators to create locked-down dashboards (e.g., a company-mandated layout with compulsory widgets) while still giving users varying degrees of customization freedom. The three levels -- `view_only`, `add_only`, and `full` -- form a hierarchy of increasing user control.", - "docsUrl": "openspec/specs/permissions/spec.md" - }, - { - "slug": "prometheus-metrics", - "title": "Prometheus Metrics", - "summary": "Expose application metrics in Prometheus text exposition format at `GET /api/metrics` for monitoring, alerting, and operational dashboards. Additionally, provide a health check endpoint at `GET /api/health` for container orchestration and load balancer readiness probes.", - "docsUrl": "openspec/specs/prometheus-metrics/spec.md" - }, - { - "slug": "quicklinks-widget", - "title": "Quicklinks Widget", - "summary": "The quicklinks widget is a built-in LaunchPad widget type that renders a flat, dense grid of icon-and-label shortcuts inside a single placement. Where the link-button widget owns one shortcut per placement and the links widget spreads grouped sections across multiple columns, the quicklinks widget targets the \"app launcher\" use case: 8–40 frequently used URLs in one widget, with admin-configurable icon size, shape, label position, columns, tile background, and hover effect. Bulk-add via CSV paste is first-class so admins can move dozens of shortcuts off a spreadsheet without typing each row.", - "docsUrl": "openspec/specs/quicklinks-widget/spec.md" - }, - { - "slug": "resource-uploads", - "title": "Resource Uploads", - "summary": "The `resource-uploads` capability owns a small mini file API for binary assets that LaunchPad widgets reference directly: dashboard icons, image-widget images, link-button icons, etc. Resources are stored in LaunchPad's app-data folder (NOT the user's Files), addressed by a stable URL, uploaded admin-only via a base64-data-URL JSON request, and served back to any logged-in user via a non-OCS streaming endpoint plus an OCS listing endpoint. SVG sanitisation is specified in the sibling `svg-sanitisation` capability.", - "docsUrl": "openspec/specs/resource-uploads/spec.md" - }, - { - "slug": "role-feature-permissions", - "title": "Role Feature Permissions", - "summary": "This capability governs which dashboard widgets and features are visible, accessible, and default-seeded for users based on their Nextcloud group (role). It ensures that staff see only the tools relevant to their job, that new users receive a role-appropriate starting layout seeded from evidence rather than a generic blank dashboard, and that attempts to access restricted features via direct URL are rejected at the API layer with a 403 response and an audit log entry.", - "docsUrl": "openspec/specs/role-feature-permissions/spec.md" - }, - { - "slug": "runtime-shell", - "title": "Runtime Shell", - "summary": "The `runtime-shell` capability owns the user-facing workspace page chrome — the mount point, the sidebar toggle, the active-dashboard label strip, the empty-state branch, and the lifecycle hooks that bind it all together. It is the page-level orchestrator that coordinates four sibling capabilities (`dashboard-switcher`, `widget-add-edit-modal`, `widget-context-menu`, `grid-layout`) and gates editing affordances based on user role and active dashboard scope.", - "docsUrl": "openspec/specs/runtime-shell/spec.md" - }, - { - "slug": "setup-wizard", - "title": "Setup Wizard", - "summary": "The Setup Wizard is a multi-step first-run configuration flow for freshly installed LaunchPad instances. It guides administrators through selecting a storage backend, setting group priority order, installing optional demo data, assigning admin roles, and configuring footer content. The wizard detects first-run state via an admin-setting flag, supports both interactive and non-interactive (CLI) flows, and ensures all choices are persisted immediately so progress is not lost.", - "docsUrl": "openspec/specs/setup-wizard/spec.md" - }, - { - "slug": "text-display-widget", - "title": "Text-Display Widget", - "summary": "The text-display widget renders user-authored text content inside a dashboard cell, with limited HTML support for inline formatting (bold, italics, links, line breaks). It is the primary \"annotation\" widget — useful for section captions, instructions, contact details, callouts.", - "docsUrl": "openspec/specs/text-display-widget/spec.md" - }, - { - "slug": "tiles", - "title": "Custom Tiles", - "summary": "Custom tiles are user-created shortcut cards that provide quick access to Nextcloud apps or external URLs. Unlike widgets (which render dynamic content from Nextcloud apps), tiles are simple, static cards with an icon, label, and link. Tiles are first created as reusable entities in the `oc_launchpad_tiles` table, then placed onto dashboards via a special tile placement mechanism that stores tile data inline on the placement. This inline-copy model means tile placements are independent snapshots -- changes to the tile definition do NOT propagate to existing placements.", - "docsUrl": "openspec/specs/tiles/spec.md" - }, - { - "slug": "video-widget", - "title": "Video Widget", - "summary": "Embed video content directly on a LaunchPad dashboard from four source types: YouTube, Vimeo, self-hosted PeerTube instances, and Nextcloud Files. Hosted-platform embeds use a sandboxed iframe with the canonical embed URL (extracted server-side at save time); internal-file embeds use a native HTML5 `