Compare commits

...

27 Commits

Author SHA1 Message Date
f6910cde2d chore(master): release 0.20.5 2026-01-17 00:26:14 +02:00
ed04879575 fix: db seeds 2026-01-17 00:22:14 +02:00
362fdc8b03 fix: allow creating multiple custom roles 2026-01-17 00:18:00 +02:00
0d98473cbf fix: category header update 2026-01-17 00:13:04 +02:00
Nextcloud bot
3242a1cad5 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2026-01-16 02:14:08 +00:00
9fac12b0c7 chore(master): release 0.20.4 2026-01-15 12:39:08 +02:00
Nextcloud bot
37a82842b1 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2026-01-15 02:03:36 +00:00
Nextcloud bot
46b2c820e8 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2026-01-14 02:02:49 +00:00
Nextcloud bot
715b2ab6ff fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2026-01-13 02:05:27 +00:00
Nextcloud bot
3ab3c1cc76 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2026-01-11 02:02:31 +00:00
3d1ddb9f26 chore(master): release 0.20.3 2026-01-10 16:42:27 +02:00
a286bbdfe9 test: add incremental db test 2026-01-10 16:39:18 +02:00
a8e158d35b test: add db integration tests 2026-01-10 15:52:21 +02:00
c3d267f122 fix: db seed transactions logic 2026-01-10 15:34:18 +02:00
c2e4ebe242 chore(deps): update dependencies 2026-01-10 15:05:21 +02:00
Nextcloud bot
679abe3fb6 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2026-01-10 02:15:18 +00:00
Nextcloud bot
043af15809 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2026-01-09 02:05:21 +00:00
407df1d423 chore(master): release 0.20.2 2026-01-09 00:53:18 +02:00
e2dcebc6ee fix: bbcode cursor positions after inserting 2026-01-08 10:30:25 +02:00
a905ce3b4c chore(master): release 0.20.1 2026-01-08 10:14:02 +02:00
c017bb3d09 fix: db seed migrations 2026-01-08 09:32:40 +02:00
Nextcloud bot
67c92c05a3 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2026-01-08 02:01:13 +00:00
e94ca2dec1 chore(deps): update dependencies 2026-01-07 22:51:20 +02:00
975744ec6f chore(master): release 0.20.0 2026-01-07 22:44:52 +02:00
cb7a03c1d5 feat: add preference to auto subscribe to replied threads 2026-01-07 22:41:00 +02:00
00e5d6d3b2 feat: add max-width to post/signature images 2026-01-07 22:40:43 +02:00
8b489b9cc3 fix: forum users tables migrations 2026-01-07 22:40:43 +02:00
78 changed files with 2385 additions and 696 deletions

View File

@@ -0,0 +1,339 @@
# Incremental migration test workflow
#
# This workflow tests that migrations work correctly when upgrading from an older version.
# It first installs the app at v0.14.0 (last version before forum_user_stats -> forum_users rename),
# then upgrades to the current version and verifies all migrations run successfully.
#
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: PHPUnit Incremental Migration
on: pull_request
permissions:
contents: read
concurrency:
group: phpunit-incremental-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
changes:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
src: ${{ steps.changes.outputs.src }}
steps:
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
continue-on-error: true
with:
filters: |
src:
- '.github/workflows/**'
- 'appinfo/**'
- 'lib/**'
- 'tests/**'
- 'composer.json'
- 'composer.lock'
incremental-pgsql:
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.src != 'false'
name: Incremental Migration (PostgreSQL)
services:
postgres:
image: ghcr.io/nextcloud/continuous-integration-postgres-16:latest # zizmor: ignore[unpinned-images]
ports:
- 4444:5432/tcp
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: rootpassword
POSTGRES_DB: nextcloud
options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5
steps:
- name: Checkout app (current)
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
path: app-current
fetch-depth: 0
- name: Detect app ID from appinfo/info.xml
run: |
APP_ID=$(grep -oP '(?<=<id>)[^<]+' app-current/appinfo/info.xml | head -1)
echo "APP_NAME=$APP_ID" >> $GITHUB_ENV
echo "Detected app ID: $APP_ID"
- name: Get supported server versions
id: versions
uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1
with:
filename: app-current/appinfo/info.xml
- name: Save current app for later
run: |
mkdir -p /tmp/app-backup
cp -r app-current /tmp/app-backup/
- name: Checkout server
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
submodules: true
repository: nextcloud/server
ref: ${{ fromJson(steps.versions.outputs.branches)[0] }}
- name: Checkout app at v0.14.0 (pre-rename baseline)
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
ref: v0.14.0
path: apps/${{ env.APP_NAME }}
- name: Set up php 8.3
uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2.35.5
with:
php-version: '8.3'
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, pgsql, pdo_pgsql
coverage: none
ini-file: development
ini-values: disable_functions=
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set up dependencies (v0.14.0)
working-directory: apps/${{ env.APP_NAME }}
run: |
composer remove nextcloud/ocp --dev --no-scripts || true
composer i --no-scripts || composer i
- name: Set up Nextcloud and install app at v0.14.0
env:
DB_PORT: 4444
run: |
mkdir data
./occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
echo "::group::Installing app at v0.14.0"
./occ app:enable --force ${{ env.APP_NAME }}
echo "::endgroup::"
echo "::group::Database tables after v0.14.0 install"
./occ db:convert-filecache-bigint --no-interaction || true
PGPASSWORD=rootpassword psql -h 127.0.0.1 -p $DB_PORT -U root -d nextcloud -c "\dt oc_forum_*"
echo "::endgroup::"
- name: Upgrade app to current version
run: |
echo "::group::Replacing app with current version"
rm -rf apps/${{ env.APP_NAME }}
cp -r /tmp/app-backup/app-current apps/${{ env.APP_NAME }}
echo "::endgroup::"
- name: Set up dependencies (current)
working-directory: apps/${{ env.APP_NAME }}
run: |
composer remove nextcloud/ocp --dev --no-scripts || true
composer i
- name: Run migrations to current version
env:
DB_PORT: 4444
run: |
echo "::group::Running upgrade migrations"
# Disable maintenance mode if it was left on
./occ maintenance:mode --off || true
# Disable and re-enable the app to trigger migrations
# This simulates what happens when a user upgrades the app
./occ app:disable ${{ env.APP_NAME }}
./occ app:enable ${{ env.APP_NAME }}
echo "::endgroup::"
echo "::group::Database tables after upgrade"
PGPASSWORD=rootpassword psql -h 127.0.0.1 -p $DB_PORT -U root -d nextcloud -c "\dt oc_forum_*"
echo "::endgroup::"
echo "::group::Checking forum_users table exists"
PGPASSWORD=rootpassword psql -h 127.0.0.1 -p $DB_PORT -U root -d nextcloud -c "SELECT COUNT(*) FROM oc_forum_users" || exit 1
echo "::endgroup::"
- name: Check PHPUnit integration script is defined
id: check_integration
continue-on-error: true
working-directory: apps/${{ env.APP_NAME }}
run: |
composer run --list | grep '^ test:integration ' | wc -l | grep 1
- name: Run Nextcloud
if: steps.check_integration.outcome == 'success'
run: php -S localhost:8080 &
- name: PHPUnit integration
if: steps.check_integration.outcome == 'success'
working-directory: apps/${{ env.APP_NAME }}
run: composer run test:integration
- name: Print logs
if: always()
run: |
cat data/nextcloud.log
incremental-mysql:
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.src != 'false'
name: Incremental Migration (MySQL)
services:
mysql:
image: ghcr.io/nextcloud/continuous-integration-mariadb-10.11:latest # zizmor: ignore[unpinned-images]
ports:
- 4444:3306/tcp
env:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: nextcloud
options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 5
steps:
- name: Checkout app (current)
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
path: app-current
fetch-depth: 0
- name: Detect app ID from appinfo/info.xml
run: |
APP_ID=$(grep -oP '(?<=<id>)[^<]+' app-current/appinfo/info.xml | head -1)
echo "APP_NAME=$APP_ID" >> $GITHUB_ENV
echo "Detected app ID: $APP_ID"
- name: Get supported server versions
id: versions
uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1
with:
filename: app-current/appinfo/info.xml
- name: Save current app for later
run: |
mkdir -p /tmp/app-backup
cp -r app-current /tmp/app-backup/
- name: Checkout server
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
submodules: true
repository: nextcloud/server
ref: ${{ fromJson(steps.versions.outputs.branches)[0] }}
- name: Checkout app at v0.14.0 (pre-rename baseline)
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
ref: v0.14.0
path: apps/${{ env.APP_NAME }}
- name: Set up php 8.3
uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2.35.5
with:
php-version: '8.3'
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, mysql, pdo_mysql
coverage: none
ini-file: development
ini-values: disable_functions=
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set up dependencies (v0.14.0)
working-directory: apps/${{ env.APP_NAME }}
run: |
composer remove nextcloud/ocp --dev --no-scripts || true
composer i --no-scripts || composer i
- name: Set up Nextcloud and install app at v0.14.0
env:
DB_PORT: 4444
run: |
mkdir data
./occ maintenance:install --verbose --database=mysql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
echo "::group::Installing app at v0.14.0"
./occ app:enable --force ${{ env.APP_NAME }}
echo "::endgroup::"
echo "::group::Database tables after v0.14.0 install"
mysql -h 127.0.0.1 -P $DB_PORT -u root -prootpassword nextcloud -e "SHOW TABLES LIKE 'oc_forum_%'"
echo "::endgroup::"
- name: Upgrade app to current version
run: |
echo "::group::Replacing app with current version"
rm -rf apps/${{ env.APP_NAME }}
cp -r /tmp/app-backup/app-current apps/${{ env.APP_NAME }}
echo "::endgroup::"
- name: Set up dependencies (current)
working-directory: apps/${{ env.APP_NAME }}
run: |
composer remove nextcloud/ocp --dev --no-scripts || true
composer i
- name: Run migrations to current version
env:
DB_PORT: 4444
run: |
echo "::group::Running upgrade migrations"
# Disable maintenance mode if it was left on
./occ maintenance:mode --off || true
# Disable and re-enable the app to trigger migrations
# This simulates what happens when a user upgrades the app
./occ app:disable ${{ env.APP_NAME }}
./occ app:enable ${{ env.APP_NAME }}
echo "::endgroup::"
echo "::group::Database tables after upgrade"
mysql -h 127.0.0.1 -P $DB_PORT -u root -prootpassword nextcloud -e "SHOW TABLES LIKE 'oc_forum_%'"
echo "::endgroup::"
echo "::group::Checking forum_users table exists"
mysql -h 127.0.0.1 -P $DB_PORT -u root -prootpassword nextcloud -e "SELECT COUNT(*) FROM oc_forum_users" || exit 1
echo "::endgroup::"
- name: Check PHPUnit integration script is defined
id: check_integration
continue-on-error: true
working-directory: apps/${{ env.APP_NAME }}
run: |
composer run --list | grep '^ test:integration ' | wc -l | grep 1
- name: Run Nextcloud
if: steps.check_integration.outcome == 'success'
run: php -S localhost:8080 &
- name: PHPUnit integration
if: steps.check_integration.outcome == 'success'
working-directory: apps/${{ env.APP_NAME }}
run: composer run test:integration
- name: Print logs
if: always()
run: |
cat data/nextcloud.log
summary:
permissions:
contents: none
runs-on: ubuntu-latest
needs: [changes, incremental-pgsql, incremental-mysql]
if: always()
name: incremental-migration-summary
steps:
- name: Summary status
run: if ${{ needs.changes.outputs.src != 'false' && (needs.incremental-pgsql.result != 'success' || needs.incremental-mysql.result != 'success') }}; then exit 1; fi

View File

@@ -174,16 +174,34 @@ jobs:
working-directory: apps/${{ env.APP_NAME }}
run: composer run test:unit
- name: Check PHPUnit integration script is defined
id: check_integration
continue-on-error: true
working-directory: apps/${{ env.APP_NAME }}
run: |
composer run --list | grep '^ test:integration ' | wc -l | grep 1
- name: Run Nextcloud
# Only run if phpunit integration config file exists
if: steps.check_integration.outcome == 'success'
run: php -S localhost:8080 &
- name: PHPUnit integration
# Only run if phpunit integration config file exists
if: steps.check_integration.outcome == 'success'
working-directory: apps/${{ env.APP_NAME }}
run: composer run test:integration
- name: Print logs
if: always()
run: |
cat data/nextcloud.log
- name: Skipped
# Fail the action when unit tests are not specified
if: steps.check_phpunit.outcome == 'failure'
# Fail the action when neither unit nor integration tests ran
if: steps.check_phpunit.outcome == 'failure' && steps.check_integration.outcome == 'failure'
run: |
echo 'PHPUnit tests are not specified in composer.json scripts'
echo 'Neither PHPUnit nor PHPUnit integration tests are specified in composer.json scripts'
exit 1
summary:

View File

@@ -1 +1 @@
{".":"0.19.7"}
{".":"0.20.5"}

View File

@@ -1,5 +1,62 @@
# Changelog
## [0.20.5](https://github.com/chenasraf/nextcloud-forum/compare/v0.20.4...v0.20.5) (2026-01-16)
### Bug Fixes
* allow creating multiple custom roles ([362fdc8](https://github.com/chenasraf/nextcloud-forum/commit/362fdc8b032299a4f32d8e4b70163a3077dcab3c))
* category header update ([0d98473](https://github.com/chenasraf/nextcloud-forum/commit/0d98473cbfc8f89a2b4962781449ccc881ab8eee))
* db seeds ([ed04879](https://github.com/chenasraf/nextcloud-forum/commit/ed048795756f474ce8126aa37fb34a7c57fa2d65))
* **l10n:** Update translations from Transifex ([3242a1c](https://github.com/chenasraf/nextcloud-forum/commit/3242a1cad51b90841ff1bde5ca7e231ea92687d9))
## [0.20.4](https://github.com/chenasraf/nextcloud-forum/compare/v0.20.3...v0.20.4) (2026-01-15)
### Bug Fixes
* **l10n:** Update translations from Transifex ([37a8284](https://github.com/chenasraf/nextcloud-forum/commit/37a82842b13012bf9bf578baf018bfa8b678d635))
* **l10n:** Update translations from Transifex ([46b2c82](https://github.com/chenasraf/nextcloud-forum/commit/46b2c820e8bec48e432bf350d56a2d85f41da115))
* **l10n:** Update translations from Transifex ([715b2ab](https://github.com/chenasraf/nextcloud-forum/commit/715b2ab6ff7a94ed431e0ca2525530e9e41f42c7))
* **l10n:** Update translations from Transifex ([3ab3c1c](https://github.com/chenasraf/nextcloud-forum/commit/3ab3c1cc76b1f55e289d96dfd681d45b6e67b171))
## [0.20.3](https://github.com/chenasraf/nextcloud-forum/compare/v0.20.2...v0.20.3) (2026-01-10)
### Bug Fixes
* db seed transactions logic ([c3d267f](https://github.com/chenasraf/nextcloud-forum/commit/c3d267f12269568e0a091cc094e043e4f1ad8cb8))
* **l10n:** Update translations from Transifex ([679abe3](https://github.com/chenasraf/nextcloud-forum/commit/679abe3fb6ce7c8545bf99875092f20cf5d468b8))
* **l10n:** Update translations from Transifex ([043af15](https://github.com/chenasraf/nextcloud-forum/commit/043af1580912d201a724313d4788d7a776e11934))
## [0.20.2](https://github.com/chenasraf/nextcloud-forum/compare/v0.20.1...v0.20.2) (2026-01-08)
### Bug Fixes
* bbcode cursor positions after inserting ([e2dcebc](https://github.com/chenasraf/nextcloud-forum/commit/e2dcebc6ee6e4d017f7f26fc86e72e6734a1f757))
## [0.20.1](https://github.com/chenasraf/nextcloud-forum/compare/v0.20.0...v0.20.1) (2026-01-08)
### Bug Fixes
* db seed migrations ([c017bb3](https://github.com/chenasraf/nextcloud-forum/commit/c017bb3d09a517c19e772420311c23a957f25cba))
* **l10n:** Update translations from Transifex ([67c92c0](https://github.com/chenasraf/nextcloud-forum/commit/67c92c05a3e7f58bbc05265087b763368653f7d3))
## [0.20.0](https://github.com/chenasraf/nextcloud-forum/compare/v0.19.7...v0.20.0) (2026-01-07)
### Features
* add max-width to post/signature images ([00e5d6d](https://github.com/chenasraf/nextcloud-forum/commit/00e5d6d3b2e14939b233a80050f645ebd7b8503a))
* add preference to auto subscribe to replied threads ([cb7a03c](https://github.com/chenasraf/nextcloud-forum/commit/cb7a03c1d51f2dc5642a47ab222b07cec6e01731))
### Bug Fixes
* forum users tables migrations ([8b489b9](https://github.com/chenasraf/nextcloud-forum/commit/8b489b9cc3919dedf1463c7c7dd54e7a8009fc6f))
## [0.19.7](https://github.com/chenasraf/nextcloud-forum/compare/v0.19.6...v0.19.7) (2026-01-05)

View File

@@ -17,13 +17,14 @@
# - JS build is delegated to your package.json scripts (tool-agnostic).
#
# Common recipes:
# make build → install deps & build
# make dist → build source + appstore tarballs
# make test → run PHP unit tests
# make lint → lint JS & PHP
# make openapi → generate OpenAPI JSON
# make sign → print signature for GitHub tarball
# make release → upload release to Nextcloud App Store
# make build → install deps & build
# make dist → build source + appstore tarballs
# make test → run PHP unit tests
# make test-integration → run PHP integration tests
# make lint → lint JS & PHP
# make openapi → generate OpenAPI JSON
# make sign → print signature for GitHub tarball
# make release → upload release to Nextcloud App Store
#
app_name=forum
@@ -242,10 +243,33 @@ test: composer
exit 1; \
fi; \
echo "\x1b[32mUsing Nextcloud root: $$NC_ROOT\x1b[0m"; \
NEXTCLOUD_ROOT="$$NC_ROOT" $(CURDIR)/vendor/phpunit/phpunit/phpunit -c tests/phpunit.xml; \
if [ -f tests/phpunit.integration.xml ]; then \
NEXTCLOUD_ROOT="$$NC_ROOT" $(CURDIR)/vendor/phpunit/phpunit/phpunit -c tests/phpunit.integration.xml; \
fi
NEXTCLOUD_ROOT="$$NC_ROOT" $(CURDIR)/vendor/phpunit/phpunit/phpunit -c tests/phpunit.xml
# test-integration:
# - Run only PHP integration tests (database-dependent tests)
# - These tests run against a real database and test migration/seeding
.PHONY: test-integration
test-integration: composer
@NC_ROOT="$(NEXTCLOUD_ROOT)"; \
if [ -n "$$NC_ROOT" ]; then \
NC_ROOT=$$(echo "$$NC_ROOT" | sed "s|^\\\~|$$HOME|" | sed "s|^~|$$HOME|"); \
fi; \
if [ -z "$$NC_ROOT" ]; then \
if [ -d "$(CURDIR)/../../../tests/bootstrap.php" ]; then \
NC_ROOT="$(CURDIR)/../../.."; \
fi; \
fi; \
if [ -z "$$NC_ROOT" ]; then \
echo "\x1b[33mCould not find Nextcloud installation.\x1b[0m"; \
echo "Set NEXTCLOUD_ROOT environment variable."; \
exit 1; \
fi; \
if [ ! -f tests/phpunit.integration.xml ]; then \
echo "\x1b[31mNo integration tests found (tests/phpunit.integration.xml missing)\x1b[0m"; \
exit 1; \
fi; \
echo "\x1b[32mUsing Nextcloud root: $$NC_ROOT\x1b[0m"; \
NEXTCLOUD_ROOT="$$NC_ROOT" $(CURDIR)/vendor/phpunit/phpunit/phpunit -c tests/phpunit.integration.xml
# test-docker:
# - Run PHP unit tests inside a Nextcloud Docker container

View File

@@ -37,7 +37,7 @@ This app is in early stages of development. While functional, you may encounter
The forum integrates seamlessly with your Nextcloud instance, using your existing users and groups for authentication and access control.
]]></description>
<version>0.19.7</version>
<version>0.20.5</version>
<licence>agpl</licence>
<author mail="contact@casraf.dev" homepage="https://casraf.dev">Chen Asraf</author>
<namespace>Forum</namespace>

View File

@@ -26,6 +26,7 @@
"cs:fix": "php-cs-fixer fix",
"psalm": "psalm --threads=1 --no-cache",
"test:unit": "phpunit tests -c tests/phpunit.xml --colors=always --fail-on-warning --fail-on-risky",
"test:integration": "phpunit tests -c tests/phpunit.integration.xml --colors=always --fail-on-warning --fail-on-risky",
"openapi": "generate-spec"
},
"require": {

27
composer.lock generated
View File

@@ -180,12 +180,12 @@
"source": {
"type": "git",
"url": "https://github.com/nextcloud-deps/ocp.git",
"reference": "45612049852ea4234dd799a4a3281a397f0749c3"
"reference": "a79703d9f38e964b003ae1cc805b6531d142fa93"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/45612049852ea4234dd799a4a3281a397f0749c3",
"reference": "45612049852ea4234dd799a4a3281a397f0749c3",
"url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/a79703d9f38e964b003ae1cc805b6531d142fa93",
"reference": "a79703d9f38e964b003ae1cc805b6531d142fa93",
"shasum": ""
},
"require": {
@@ -220,7 +220,7 @@
"issues": "https://github.com/nextcloud-deps/ocp/issues",
"source": "https://github.com/nextcloud-deps/ocp/tree/stable32"
},
"time": "2025-12-16T00:55:52+00:00"
"time": "2026-01-09T00:57:52+00:00"
},
{
"name": "nikic/php-parser",
@@ -1035,12 +1035,12 @@
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
"reference": "5ba14c800ff89c74333c22d56ca1c1f35c424805"
"reference": "ccfd723dc03e9864008d011603c412910180d7a6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/5ba14c800ff89c74333c22d56ca1c1f35c424805",
"reference": "5ba14c800ff89c74333c22d56ca1c1f35c424805",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ccfd723dc03e9864008d011603c412910180d7a6",
"reference": "ccfd723dc03e9864008d011603c412910180d7a6",
"shasum": ""
},
"conflict": {
@@ -1173,10 +1173,11 @@
"contao/core-bundle": "<4.13.57|>=5,<5.3.42|>=5.4,<5.6.5",
"contao/listing-bundle": ">=3,<=3.5.30|>=4,<4.4.8",
"contao/managed-edition": "<=1.5",
"coreshop/core-shop": "<=4.1.7",
"corveda/phpsandbox": "<1.3.5",
"cosenary/instagram": "<=2.3",
"couleurcitron/tarteaucitron-wp": "<0.3",
"craftcms/cms": "<=4.16.5|>=5,<=5.8.6",
"craftcms/cms": "<=4.16.16|>=5,<=5.8.20",
"croogo/croogo": "<=4.0.7",
"cuyz/valinor": "<0.12",
"czim/file-handling": "<1.5|>=2,<2.3",
@@ -1224,7 +1225,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.102|>=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.4.9|>=10.5,<10.5.6|>=11,<11.1.9|>=11.2,<11.2.8",
"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",
@@ -1339,7 +1340,7 @@
"geshi/geshi": "<=1.0.9.1",
"getformwork/formwork": "<2.2",
"getgrav/grav": "<1.11.0.0-beta1",
"getkirby/cms": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1|>=5,<5.1.4",
"getkirby/cms": "<3.9.8.3-dev|>=3.10,<3.10.1.2-dev|>=4,<4.7.1|>=5,<=5.2.1",
"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",
@@ -1568,7 +1569,7 @@
"october/cms": "<1.0.469|==1.0.469|==1.0.471|==1.1.1",
"october/october": "<3.7.5",
"october/rain": "<1.0.472|>=1.1,<1.1.2",
"october/system": "<3.7.5",
"october/system": "<=3.7.12|>=4,<=4.0.11",
"oliverklee/phpunit": "<3.5.15",
"omeka/omeka-s": "<4.0.3",
"onelogin/php-saml": "<2.21.1|>=3,<3.8.1|>=4,<4.3.1",
@@ -1681,7 +1682,7 @@
"rap2hpoutre/laravel-log-viewer": "<0.13",
"react/http": ">=0.7,<1.9",
"really-simple-plugins/complianz-gdpr": "<6.4.2",
"redaxo/source": "<5.20.1",
"redaxo/source": "<=5.20.1",
"remdex/livehelperchat": "<4.29",
"renolit/reint-downloadmanager": "<4.0.2|>=5,<5.0.1",
"reportico-web/reportico": "<=8.1",
@@ -2034,7 +2035,7 @@
"type": "tidelift"
}
],
"time": "2026-01-02T22:05:49+00:00"
"time": "2026-01-09T19:06:26+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@@ -8,11 +8,11 @@ OC.L10N.register(
"General" : "Агульныя",
"Support" : "Падтрымка",
"Attachment" : "Далучэнне",
"Welcome to Nextcloud Forums" : "Вітаем на Форумах Nextcloud",
"Welcome to the Nextcloud Forums!" : "Вітаем на Форумах Nextcloud!",
"Bold text" : "Тоўсты тэкст",
"Italic text" : "Тэкст курсівам",
"Underlined text" : "Падкрэслены тэкст",
"Welcome to Nextcloud Forums" : "Вітаем на Форумах Nextcloud",
"Forum" : "Форум",
"Welcome to the forum!" : "Вітаем на форуме!",
"Deleted user" : "Выдалены карыстальнік",

View File

@@ -6,11 +6,11 @@
"General" : "Агульныя",
"Support" : "Падтрымка",
"Attachment" : "Далучэнне",
"Welcome to Nextcloud Forums" : "Вітаем на Форумах Nextcloud",
"Welcome to the Nextcloud Forums!" : "Вітаем на Форумах Nextcloud!",
"Bold text" : "Тоўсты тэкст",
"Italic text" : "Тэкст курсівам",
"Underlined text" : "Падкрэслены тэкст",
"Welcome to Nextcloud Forums" : "Вітаем на Форумах Nextcloud",
"Forum" : "Форум",
"Welcome to the forum!" : "Вітаем на форуме!",
"Deleted user" : "Выдалены карыстальнік",

View File

@@ -20,7 +20,6 @@ OC.L10N.register(
"Hidden content" : "Skrytý obsah",
"Spoilers" : "Spoilery",
"Attachment" : "Příloha",
"Welcome to Nextcloud Forums" : "Vítejte v Nextcloud fórech",
"Welcome to the Nextcloud Forums!" : "Vítejte v Nextcloud fórech!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Toto je komunitou řízené fórum, vestavěné přímo do vámi využívané instance Nextcloud. Je možné zde probírat témata, sdílet nápady a spolupracovat s ostatními uživateli.",
"Features:" : "Funkce:",
@@ -36,6 +35,7 @@ OC.L10N.register(
"Italic text" : "Skloněný text",
"Underlined text" : "Podtržený text",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Neváhejte zahájit novou diskuzi nebo odpovězte na existující vlákna. Vesele pište příspěvky!.",
"Welcome to Nextcloud Forums" : "Vítejte v Nextcloud fórech",
"Forum" : "Diskuzní fórum",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} nová odpověď v {thread} ","{count} nové odpovědi v {thread} ","{count} nových odpovědí v {thread} ","{count} nové odpovědi v {thread} "],
"{user} mentioned you in {thread}" : "{user} vás zmínil(a) v {thread}",

View File

@@ -18,7 +18,6 @@
"Hidden content" : "Skrytý obsah",
"Spoilers" : "Spoilery",
"Attachment" : "Příloha",
"Welcome to Nextcloud Forums" : "Vítejte v Nextcloud fórech",
"Welcome to the Nextcloud Forums!" : "Vítejte v Nextcloud fórech!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Toto je komunitou řízené fórum, vestavěné přímo do vámi využívané instance Nextcloud. Je možné zde probírat témata, sdílet nápady a spolupracovat s ostatními uživateli.",
"Features:" : "Funkce:",
@@ -34,6 +33,7 @@
"Italic text" : "Skloněný text",
"Underlined text" : "Podtržený text",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Neváhejte zahájit novou diskuzi nebo odpovězte na existující vlákna. Vesele pište příspěvky!.",
"Welcome to Nextcloud Forums" : "Vítejte v Nextcloud fórech",
"Forum" : "Diskuzní fórum",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} nová odpověď v {thread} ","{count} nové odpovědi v {thread} ","{count} nových odpovědí v {thread} ","{count} nové odpovědi v {thread} "],
"{user} mentioned you in {thread}" : "{user} vás zmínil(a) v {thread}",

View File

@@ -20,7 +20,6 @@ OC.L10N.register(
"Hidden content" : "Versteckter Inhalt",
"Spoilers" : "Spoiler",
"Attachment" : "Anhang",
"Welcome to Nextcloud Forums" : "Willkommen in den Nextcloud Foren",
"Welcome to the Nextcloud Forums!" : "Willkommen in den Nextcloud Foren!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Dies ist ein Community-Forum, das direkt in deine Nextcloud-Instanz integriert ist. Hier kannst du Themen diskutieren, Ideen austauschen und mit anderen Nutzern zusammenarbeiten.",
"Features:" : "Funktionen:",
@@ -36,6 +35,7 @@ OC.L10N.register(
"Italic text" : "Kursiver Text",
"Underlined text" : "Unterstrichener Text",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Gerne eine neue Diskussion starten oder auf bestehende Beiträge antworten. Viel Spaß beim Posten!",
"Welcome to Nextcloud Forums" : "Willkommen in den Nextcloud Foren",
"Forum" : "Forum",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} neue Antwort in {thread}","{count} neue Antworten in {thread}"],
"{user} mentioned you in {thread}" : "{user} hat dich in {thread} erwähnt",
@@ -282,6 +282,8 @@ OC.L10N.register(
"Configure how you receive notifications" : "Einrichten, wie du Benachrichtigungen erhältst",
"Auto-subscribe to threads I create" : "Themen die ich erstellt habe, automatisch abonnieren",
"When enabled, you will automatically receive notifications for replies to threads you create" : "Wenn diese Option aktiviert ist, erhältst du automatisch Benachrichtigungen für Antworten auf von dir erstellte Themen",
"Auto-subscribe to threads I reply to" : "Themen auf die ich geantwortet habe, automatisch abonnieren",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "Wenn diese Option aktiviert ist, erhältst du automatisch Benachrichtigungen für Antworten in Themen, in denen du geantwortet hast",
"Files" : "Dateien",
"Configure file upload settings" : "Einstellungen für das Hochladen von Dateien",
"Upload directory" : "Hochladeverzeichnis",

View File

@@ -18,7 +18,6 @@
"Hidden content" : "Versteckter Inhalt",
"Spoilers" : "Spoiler",
"Attachment" : "Anhang",
"Welcome to Nextcloud Forums" : "Willkommen in den Nextcloud Foren",
"Welcome to the Nextcloud Forums!" : "Willkommen in den Nextcloud Foren!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Dies ist ein Community-Forum, das direkt in deine Nextcloud-Instanz integriert ist. Hier kannst du Themen diskutieren, Ideen austauschen und mit anderen Nutzern zusammenarbeiten.",
"Features:" : "Funktionen:",
@@ -34,6 +33,7 @@
"Italic text" : "Kursiver Text",
"Underlined text" : "Unterstrichener Text",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Gerne eine neue Diskussion starten oder auf bestehende Beiträge antworten. Viel Spaß beim Posten!",
"Welcome to Nextcloud Forums" : "Willkommen in den Nextcloud Foren",
"Forum" : "Forum",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} neue Antwort in {thread}","{count} neue Antworten in {thread}"],
"{user} mentioned you in {thread}" : "{user} hat dich in {thread} erwähnt",
@@ -280,6 +280,8 @@
"Configure how you receive notifications" : "Einrichten, wie du Benachrichtigungen erhältst",
"Auto-subscribe to threads I create" : "Themen die ich erstellt habe, automatisch abonnieren",
"When enabled, you will automatically receive notifications for replies to threads you create" : "Wenn diese Option aktiviert ist, erhältst du automatisch Benachrichtigungen für Antworten auf von dir erstellte Themen",
"Auto-subscribe to threads I reply to" : "Themen auf die ich geantwortet habe, automatisch abonnieren",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "Wenn diese Option aktiviert ist, erhältst du automatisch Benachrichtigungen für Antworten in Themen, in denen du geantwortet hast",
"Files" : "Dateien",
"Configure file upload settings" : "Einstellungen für das Hochladen von Dateien",
"Upload directory" : "Hochladeverzeichnis",

View File

@@ -20,7 +20,6 @@ OC.L10N.register(
"Hidden content" : "Versteckter Inhalt",
"Spoilers" : "Spoilerwarnung",
"Attachment" : "Anhang",
"Welcome to Nextcloud Forums" : "Willkommen in den Nextcloud Foren",
"Welcome to the Nextcloud Forums!" : "Willkommen in den Nextcloud Foren!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Dies ist ein Community-Forum, das direkt in Ihre Nextcloud-Instanz integriert ist. Hier können Sie Themen diskutieren, Ideen austauschen und mit anderen Nutzern zusammenarbeiten.",
"Features:" : "Funktionen:",
@@ -36,6 +35,7 @@ OC.L10N.register(
"Italic text" : "Kursiver Text",
"Underlined text" : "Unterstrichener Text",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Gerne eine neue Diskussion starten oder auf bestehende Beiträge antworten. Viel Spaß beim Posten!",
"Welcome to Nextcloud Forums" : "Willkommen in den Nextcloud Foren",
"Forum" : "Forum",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} neue Antwort in {thread}","{count} neue Antworten in {thread}"],
"{user} mentioned you in {thread}" : "{user} hat Sie in {thread} erwähnt",
@@ -282,6 +282,8 @@ OC.L10N.register(
"Configure how you receive notifications" : "Einrichten, wie Sie Benachrichtigungen erhalten",
"Auto-subscribe to threads I create" : "Themen die ich erstellt habe, automatisch abonnieren",
"When enabled, you will automatically receive notifications for replies to threads you create" : "Wenn diese Option aktiviert ist, erhalten Sie automatisch Benachrichtigungen für Antworten auf von Ihnen erstellte Themen",
"Auto-subscribe to threads I reply to" : "Themen auf die ich geantwortet habe, automatisch abonnieren",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "Wenn diese Option aktiviert ist, erhalten Sie automatisch Benachrichtigungen für Antworten in Themen, in denen Sie geantwortet haben",
"Files" : "Dateien",
"Configure file upload settings" : "Einstellungen für das Hochladen von Dateien",
"Upload directory" : "Hochladeverzeichnis",

View File

@@ -18,7 +18,6 @@
"Hidden content" : "Versteckter Inhalt",
"Spoilers" : "Spoilerwarnung",
"Attachment" : "Anhang",
"Welcome to Nextcloud Forums" : "Willkommen in den Nextcloud Foren",
"Welcome to the Nextcloud Forums!" : "Willkommen in den Nextcloud Foren!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Dies ist ein Community-Forum, das direkt in Ihre Nextcloud-Instanz integriert ist. Hier können Sie Themen diskutieren, Ideen austauschen und mit anderen Nutzern zusammenarbeiten.",
"Features:" : "Funktionen:",
@@ -34,6 +33,7 @@
"Italic text" : "Kursiver Text",
"Underlined text" : "Unterstrichener Text",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Gerne eine neue Diskussion starten oder auf bestehende Beiträge antworten. Viel Spaß beim Posten!",
"Welcome to Nextcloud Forums" : "Willkommen in den Nextcloud Foren",
"Forum" : "Forum",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} neue Antwort in {thread}","{count} neue Antworten in {thread}"],
"{user} mentioned you in {thread}" : "{user} hat Sie in {thread} erwähnt",
@@ -280,6 +280,8 @@
"Configure how you receive notifications" : "Einrichten, wie Sie Benachrichtigungen erhalten",
"Auto-subscribe to threads I create" : "Themen die ich erstellt habe, automatisch abonnieren",
"When enabled, you will automatically receive notifications for replies to threads you create" : "Wenn diese Option aktiviert ist, erhalten Sie automatisch Benachrichtigungen für Antworten auf von Ihnen erstellte Themen",
"Auto-subscribe to threads I reply to" : "Themen auf die ich geantwortet habe, automatisch abonnieren",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "Wenn diese Option aktiviert ist, erhalten Sie automatisch Benachrichtigungen für Antworten in Themen, in denen Sie geantwortet haben",
"Files" : "Dateien",
"Configure file upload settings" : "Einstellungen für das Hochladen von Dateien",
"Upload directory" : "Hochladeverzeichnis",

View File

@@ -20,7 +20,6 @@ OC.L10N.register(
"Hidden content" : "Hidden content",
"Spoilers" : "Spoilers",
"Attachment" : "Attachment",
"Welcome to Nextcloud Forums" : "Welcome to Nextcloud Forums",
"Welcome to the Nextcloud Forums!" : "Welcome to the Nextcloud Forums!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users.",
"Features:" : "Features:",
@@ -36,6 +35,7 @@ OC.L10N.register(
"Italic text" : "Italic text",
"Underlined text" : "Underlined text",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Feel free to start a new discussion or reply to existing threads. Happy posting!",
"Welcome to Nextcloud Forums" : "Welcome to Nextcloud Forums",
"Forum" : "Forum",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} new reply in {thread}","{count} new replies in {thread}"],
"{user} mentioned you in {thread}" : "{user} mentioned you in {thread}",

View File

@@ -18,7 +18,6 @@
"Hidden content" : "Hidden content",
"Spoilers" : "Spoilers",
"Attachment" : "Attachment",
"Welcome to Nextcloud Forums" : "Welcome to Nextcloud Forums",
"Welcome to the Nextcloud Forums!" : "Welcome to the Nextcloud Forums!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users.",
"Features:" : "Features:",
@@ -34,6 +33,7 @@
"Italic text" : "Italic text",
"Underlined text" : "Underlined text",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Feel free to start a new discussion or reply to existing threads. Happy posting!",
"Welcome to Nextcloud Forums" : "Welcome to Nextcloud Forums",
"Forum" : "Forum",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} new reply in {thread}","{count} new replies in {thread}"],
"{user} mentioned you in {thread}" : "{user} mentioned you in {thread}",

View File

@@ -2,11 +2,15 @@ OC.L10N.register(
"forum",
{
"Admin" : "Administrador",
"Administrator role with full permissions" : "Rol de administrador con permisos completos",
"Moderator" : "Moderador",
"Moderator role with elevated permissions" : "Rol de moderador con permisos elevados",
"User" : "Usuario",
"Default user role with basic permissions" : "Rol de usuario por defecto con permisos básicos",
"Guest" : "Invitado",
"Guest role for unauthenticated users with read-only access" : "Rol de invitado para usuarios sin autenticar con acceso de solo lectura",
"General" : "General",
"General discussion categories" : "Categorías de discusión general",
"Support" : "Soporte",
"Bold text" : "Texto en negrita",
"Underlined text" : "Texto subrayado",

View File

@@ -1,10 +1,14 @@
{ "translations": {
"Admin" : "Administrador",
"Administrator role with full permissions" : "Rol de administrador con permisos completos",
"Moderator" : "Moderador",
"Moderator role with elevated permissions" : "Rol de moderador con permisos elevados",
"User" : "Usuario",
"Default user role with basic permissions" : "Rol de usuario por defecto con permisos básicos",
"Guest" : "Invitado",
"Guest role for unauthenticated users with read-only access" : "Rol de invitado para usuarios sin autenticar con acceso de solo lectura",
"General" : "General",
"General discussion categories" : "Categorías de discusión general",
"Support" : "Soporte",
"Bold text" : "Texto en negrita",
"Underlined text" : "Texto subrayado",

View File

@@ -18,7 +18,6 @@ OC.L10N.register(
"Inline code" : "Código en línea",
"Spoilers" : "Spoilers",
"Attachment" : "Adjunto",
"Welcome to Nextcloud Forums" : "Bienvenido a los Foros de Nextcloud",
"Welcome to the Nextcloud Forums!" : "¡Bienvenido a los Foros de Nextcloud!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Este es un foro impulsado por la comunidad integrado directamente en tu instancia de Nextcloud. Aquí puedes discutir temas, compartir ideas y colaborar con otros usuarios.",
"Features:" : "Funciones:",
@@ -34,6 +33,7 @@ OC.L10N.register(
"Italic text" : "Texto en cursiva",
"Underlined text" : "Texto subrayado",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "No dudes en iniciar una nueva discusión o responder a hilos existentes. ¡Feliz publicación!",
"Welcome to Nextcloud Forums" : "Bienvenido a los Foros de Nextcloud",
"Forum" : "Foro",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} nueva respuesta en {thread}","{count} nuevas respuestas en {thread}","{count} nuevas respuestas en {thread}"],
"Welcome to the forum!" : "¡Bienvenido al foro!",

View File

@@ -16,7 +16,6 @@
"Inline code" : "Código en línea",
"Spoilers" : "Spoilers",
"Attachment" : "Adjunto",
"Welcome to Nextcloud Forums" : "Bienvenido a los Foros de Nextcloud",
"Welcome to the Nextcloud Forums!" : "¡Bienvenido a los Foros de Nextcloud!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Este es un foro impulsado por la comunidad integrado directamente en tu instancia de Nextcloud. Aquí puedes discutir temas, compartir ideas y colaborar con otros usuarios.",
"Features:" : "Funciones:",
@@ -32,6 +31,7 @@
"Italic text" : "Texto en cursiva",
"Underlined text" : "Texto subrayado",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "No dudes en iniciar una nueva discusión o responder a hilos existentes. ¡Feliz publicación!",
"Welcome to Nextcloud Forums" : "Bienvenido a los Foros de Nextcloud",
"Forum" : "Foro",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} nueva respuesta en {thread}","{count} nuevas respuestas en {thread}","{count} nuevas respuestas en {thread}"],
"Welcome to the forum!" : "¡Bienvenido al foro!",

View File

@@ -20,7 +20,6 @@ OC.L10N.register(
"Hidden content" : "Peidetud sisu",
"Spoilers" : "Spoilerid/tujurikkujad",
"Attachment" : "Manus",
"Welcome to Nextcloud Forums" : "Tere tulemast kasutama Nextcloudi Foorumit",
"Welcome to the Nextcloud Forums!" : "Tere tulemast kasutama rakendust Nextcloudi Foorumid!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "See on kogukonnapõhine foorum, mis toimib otse sinu Nextcloudi serveris. Siin saad arutleda erinevatel teemadel, jagada mõtteid ning osaleda ühistöös.",
"Features:" : "Foorumi võimalused:",
@@ -36,6 +35,7 @@ OC.L10N.register(
"Italic text" : "Tekst kaldkirjas",
"Underlined text" : "Allajoonitud tekst",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Alusta uut keskustelu või vasta olemasolevale jutulõngale. Soovime sulle toimekat suhtlust!",
"Welcome to Nextcloud Forums" : "Tere tulemast kasutama Nextcloudi Foorumit",
"Forum" : "Foorum",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} uus vastus jutulõngas {thread}","{count} uut vastust jutulõngas {thread}"],
"{user} mentioned you in {thread}" : "{user} mainis sind jutulõngas „{thread}“",
@@ -281,6 +281,8 @@ OC.L10N.register(
"Configure how you receive notifications" : "Seadista endale teavituste saatmise viisi",
"Auto-subscribe to threads I create" : "Telli minu loodud jutulõngad automaatselt",
"When enabled, you will automatically receive notifications for replies to threads you create" : "Kui see eelistus on kasutusel, siis saad automaatselt teavituse sinu loodud jutulõngade vastuste puhul",
"Auto-subscribe to threads I reply to" : "Telli minu poolt vastatud jutulõngad automaatselt",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "Kui see eelistus on kasutusel, siis saad automaatselt teavituse nende jutulõngade kohta, kuhu oled vastuse kirjutanud",
"Files" : "Failid",
"Configure file upload settings" : "Seadista failide üleslaadimise seadistusi",
"Upload directory" : "Üleslaadimiskaust",

View File

@@ -18,7 +18,6 @@
"Hidden content" : "Peidetud sisu",
"Spoilers" : "Spoilerid/tujurikkujad",
"Attachment" : "Manus",
"Welcome to Nextcloud Forums" : "Tere tulemast kasutama Nextcloudi Foorumit",
"Welcome to the Nextcloud Forums!" : "Tere tulemast kasutama rakendust Nextcloudi Foorumid!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "See on kogukonnapõhine foorum, mis toimib otse sinu Nextcloudi serveris. Siin saad arutleda erinevatel teemadel, jagada mõtteid ning osaleda ühistöös.",
"Features:" : "Foorumi võimalused:",
@@ -34,6 +33,7 @@
"Italic text" : "Tekst kaldkirjas",
"Underlined text" : "Allajoonitud tekst",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Alusta uut keskustelu või vasta olemasolevale jutulõngale. Soovime sulle toimekat suhtlust!",
"Welcome to Nextcloud Forums" : "Tere tulemast kasutama Nextcloudi Foorumit",
"Forum" : "Foorum",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} uus vastus jutulõngas {thread}","{count} uut vastust jutulõngas {thread}"],
"{user} mentioned you in {thread}" : "{user} mainis sind jutulõngas „{thread}“",
@@ -279,6 +279,8 @@
"Configure how you receive notifications" : "Seadista endale teavituste saatmise viisi",
"Auto-subscribe to threads I create" : "Telli minu loodud jutulõngad automaatselt",
"When enabled, you will automatically receive notifications for replies to threads you create" : "Kui see eelistus on kasutusel, siis saad automaatselt teavituse sinu loodud jutulõngade vastuste puhul",
"Auto-subscribe to threads I reply to" : "Telli minu poolt vastatud jutulõngad automaatselt",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "Kui see eelistus on kasutusel, siis saad automaatselt teavituse nende jutulõngade kohta, kuhu oled vastuse kirjutanud",
"Files" : "Failid",
"Configure file upload settings" : "Seadista failide üleslaadimise seadistusi",
"Upload directory" : "Üleslaadimiskaust",

View File

@@ -20,7 +20,6 @@ OC.L10N.register(
"Hidden content" : "Ábhar i bhfolach",
"Spoilers" : "Spoiléirí",
"Attachment" : "Ceangaltán",
"Welcome to Nextcloud Forums" : "Fáilte go Fóraim Nextcloud",
"Welcome to the Nextcloud Forums!" : "Fáilte go dtí Fóraim Nextcloud!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Is fóram pobail-thiomáinte é seo atá tógtha isteach i do chás Nextcloud. Anseo is féidir leat topaicí a phlé, smaointe a roinnt agus comhoibriú le húsáideoirí eile.",
"Features:" : "Gnéithe:",
@@ -36,6 +35,7 @@ OC.L10N.register(
"Italic text" : "Téacs iodálach",
"Underlined text" : "Téacs a bhfuil líne faoi",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Ná bíodh drogall ort plé nua a thosú nó freagra a thabhairt ar shnáitheanna atá ann cheana féin. Go n-éirí leat ag postáil!",
"Welcome to Nextcloud Forums" : "Fáilte go Fóraim Nextcloud",
"Forum" : "Fóram",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count}freagra nua i {thread}","{count} freagraí nua i {thread}","{count} freagraí nua i {thread}","{count} freagraí nua i {thread}","{count} freagraí nua i {thread}"],
"{user} mentioned you in {thread}" : "Luaigh {user} thú i {thread}",
@@ -282,6 +282,8 @@ OC.L10N.register(
"Configure how you receive notifications" : "Cumraigh conas a fhaigheann tú fógraí",
"Auto-subscribe to threads I create" : "Liostáil go huathoibríoch le snáitheanna a chruthaím",
"When enabled, you will automatically receive notifications for replies to threads you create" : "Nuair a bheidh sé cumasaithe, gheobhaidh tú fógraí go huathoibríoch le haghaidh freagraí ar shnáitheanna a chruthaíonn tú",
"Auto-subscribe to threads I reply to" : "Liostáil go huathoibríoch le snáitheanna a bhfreagraím orthu",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "Nuair a bheidh sé cumasaithe, gheobhaidh tú fógraí go huathoibríoch faoi fhreagraí nua i snáitheanna ar fhreagair tú iad",
"Files" : "Comhaid",
"Configure file upload settings" : "Cumraigh socruithe uaslódála comhad",
"Upload directory" : "Uaslódáil eolaire",

View File

@@ -18,7 +18,6 @@
"Hidden content" : "Ábhar i bhfolach",
"Spoilers" : "Spoiléirí",
"Attachment" : "Ceangaltán",
"Welcome to Nextcloud Forums" : "Fáilte go Fóraim Nextcloud",
"Welcome to the Nextcloud Forums!" : "Fáilte go dtí Fóraim Nextcloud!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Is fóram pobail-thiomáinte é seo atá tógtha isteach i do chás Nextcloud. Anseo is féidir leat topaicí a phlé, smaointe a roinnt agus comhoibriú le húsáideoirí eile.",
"Features:" : "Gnéithe:",
@@ -34,6 +33,7 @@
"Italic text" : "Téacs iodálach",
"Underlined text" : "Téacs a bhfuil líne faoi",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Ná bíodh drogall ort plé nua a thosú nó freagra a thabhairt ar shnáitheanna atá ann cheana féin. Go n-éirí leat ag postáil!",
"Welcome to Nextcloud Forums" : "Fáilte go Fóraim Nextcloud",
"Forum" : "Fóram",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count}freagra nua i {thread}","{count} freagraí nua i {thread}","{count} freagraí nua i {thread}","{count} freagraí nua i {thread}","{count} freagraí nua i {thread}"],
"{user} mentioned you in {thread}" : "Luaigh {user} thú i {thread}",
@@ -280,6 +280,8 @@
"Configure how you receive notifications" : "Cumraigh conas a fhaigheann tú fógraí",
"Auto-subscribe to threads I create" : "Liostáil go huathoibríoch le snáitheanna a chruthaím",
"When enabled, you will automatically receive notifications for replies to threads you create" : "Nuair a bheidh sé cumasaithe, gheobhaidh tú fógraí go huathoibríoch le haghaidh freagraí ar shnáitheanna a chruthaíonn tú",
"Auto-subscribe to threads I reply to" : "Liostáil go huathoibríoch le snáitheanna a bhfreagraím orthu",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "Nuair a bheidh sé cumasaithe, gheobhaidh tú fógraí go huathoibríoch faoi fhreagraí nua i snáitheanna ar fhreagair tú iad",
"Files" : "Comhaid",
"Configure file upload settings" : "Cumraigh socruithe uaslódála comhad",
"Upload directory" : "Uaslódáil eolaire",

View File

@@ -20,7 +20,6 @@ OC.L10N.register(
"Hidden content" : "Contido agochado",
"Spoilers" : "Destripes",
"Attachment" : "Anexo",
"Welcome to Nextcloud Forums" : "Dámoslle a benvida a os Foros de Nextcloud",
"Welcome to the Nextcloud Forums!" : "Dámoslle a benvida a os Foros de Nextcloud!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Este é un foro impulsado pola comunidade integrado directamente na súa instancia de Nextcloud. Aquí pode debater temas, compartir ideas e colaborar con outros usuarios.",
"Features:" : "Funcionalidades:",
@@ -36,6 +35,7 @@ OC.L10N.register(
"Italic text" : "Texto en cursiva",
"Underlined text" : "Texto subliñado",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Síntase libre de iniciar un novo debate ou de responder a fíos existentes. Feliz publicación!",
"Welcome to Nextcloud Forums" : "Dámoslle a benvida a os Foros de Nextcloud",
"Forum" : "Foro",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} nova resposta en {thread}","{count} novas respostas en {thread}"],
"{user} mentioned you in {thread}" : "{user} mencionouno a Vde. en {thread}",
@@ -93,7 +93,7 @@ OC.L10N.register(
"Pick file from Nextcloud" : "Seleccionar un ficheiro en Nextcloud",
"Upload file to Nextcloud" : "Enviar un ficheiro a Nextcloud",
"Uploading file …" : "Enviando o ficheiro…",
"Upload failed" : "Produciuse algún fallo no envío",
"Upload failed" : "Produciuse un fallo no envío",
"Close" : "Pechar",
"Pick a file to attach" : "Escolla un ficheiro para anexar",
"Failed to upload file" : "Produciuse un fallo ao enviar o ficheiro",
@@ -280,8 +280,10 @@ OC.L10N.register(
"Error loading preferences" : "Produciuse un erro ao cargar as preferencias",
"Notifications" : "Notificacións",
"Configure how you receive notifications" : "Configure como quere recibir as notificacións",
"Auto-subscribe to threads I create" : "Subscríbirse automaticamente aos fíos que creou",
"Auto-subscribe to threads I create" : "Subscribirme automaticamente aos fíos que creo",
"When enabled, you will automatically receive notifications for replies to threads you create" : "Cando estea activado, recibirá automaticamente notificacións das respostas aos fíos que cree",
"Auto-subscribe to threads I reply to" : "Subscribirme automaticamente aos fíos que respondo",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "Cando estea activado, recibirá automaticamente notificacións de novas respostas nos fíos aos que respondeu",
"Files" : "Ficheiros",
"Configure file upload settings" : "Configurar os axustes de envío de ficheiros",
"Upload directory" : "Directorio ao que enviar",

View File

@@ -18,7 +18,6 @@
"Hidden content" : "Contido agochado",
"Spoilers" : "Destripes",
"Attachment" : "Anexo",
"Welcome to Nextcloud Forums" : "Dámoslle a benvida a os Foros de Nextcloud",
"Welcome to the Nextcloud Forums!" : "Dámoslle a benvida a os Foros de Nextcloud!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Este é un foro impulsado pola comunidade integrado directamente na súa instancia de Nextcloud. Aquí pode debater temas, compartir ideas e colaborar con outros usuarios.",
"Features:" : "Funcionalidades:",
@@ -34,6 +33,7 @@
"Italic text" : "Texto en cursiva",
"Underlined text" : "Texto subliñado",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Síntase libre de iniciar un novo debate ou de responder a fíos existentes. Feliz publicación!",
"Welcome to Nextcloud Forums" : "Dámoslle a benvida a os Foros de Nextcloud",
"Forum" : "Foro",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} nova resposta en {thread}","{count} novas respostas en {thread}"],
"{user} mentioned you in {thread}" : "{user} mencionouno a Vde. en {thread}",
@@ -91,7 +91,7 @@
"Pick file from Nextcloud" : "Seleccionar un ficheiro en Nextcloud",
"Upload file to Nextcloud" : "Enviar un ficheiro a Nextcloud",
"Uploading file …" : "Enviando o ficheiro…",
"Upload failed" : "Produciuse algún fallo no envío",
"Upload failed" : "Produciuse un fallo no envío",
"Close" : "Pechar",
"Pick a file to attach" : "Escolla un ficheiro para anexar",
"Failed to upload file" : "Produciuse un fallo ao enviar o ficheiro",
@@ -278,8 +278,10 @@
"Error loading preferences" : "Produciuse un erro ao cargar as preferencias",
"Notifications" : "Notificacións",
"Configure how you receive notifications" : "Configure como quere recibir as notificacións",
"Auto-subscribe to threads I create" : "Subscríbirse automaticamente aos fíos que creou",
"Auto-subscribe to threads I create" : "Subscribirme automaticamente aos fíos que creo",
"When enabled, you will automatically receive notifications for replies to threads you create" : "Cando estea activado, recibirá automaticamente notificacións das respostas aos fíos que cree",
"Auto-subscribe to threads I reply to" : "Subscribirme automaticamente aos fíos que respondo",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "Cando estea activado, recibirá automaticamente notificacións de novas respostas nos fíos aos que respondeu",
"Files" : "Ficheiros",
"Configure file upload settings" : "Configurar os axustes de envío de ficheiros",
"Upload directory" : "Directorio ao que enviar",

View File

@@ -1,14 +1,14 @@
OC.L10N.register(
"forum",
{
"Admin" : "מנהל",
"Administrator role with full permissions" : "תפקיד מנהל עם הרשאות מלאות",
"Admin" : "אדמין",
"Administrator role with full permissions" : "תפקיד אדמין עם הרשאות מלאות",
"Moderator" : "מפקח",
"Moderator role with elevated permissions" : "תפקיד מפקח עם הרשאות מוגבהות",
"User" : "משתמש",
"Default user role with basic permissions" : "תפקיד משתמש רגיל עם הרשאות בסיסיות",
"Default user role with basic permissions" : "תפקיד משתמש ברירת מחדל עם הרשאות בסיסיות",
"Guest" : "אורח",
"Guest role for unauthenticated users with read-only access" : "תפקיד אורח עם הרשאות גישה לקריאה בלבד",
"Guest role for unauthenticated users with read-only access" : "תפקיד אורח למשתמשים לא-מחוברים עם הרשאות גישה לקריאה בלבד",
"General" : "כללי",
"General discussion categories" : "קטגוריות לדיונים כלליים",
"General discussions" : "דיונים כלליים",
@@ -20,24 +20,29 @@ OC.L10N.register(
"Hidden content" : "תוכן מוסתר",
"Spoilers" : "ספוילרים",
"Attachment" : "קובץ מצורף",
"Welcome to Nextcloud Forums" : "ברוכים הבאים לפורום Nextcloud",
"Welcome to the Nextcloud Forums!" : "ברוכים הבאים לפורום של Nextcloud!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "זהו פורום מונחה-קהילה מובנה ישירות ל-Nextcloud שלך. כאן ניתן להתדיין בנושאים שונים, לחלוק רעיונות ולשתף פעולה עם משתמשים אחרים.",
"Features:" : "יכולות:",
"Create and reply to threads" : "יצירה והגבה לשרשורים",
"Organize discussions by categories" : "אירגון דיונים על-פי קטגוריות",
"Use BBCode for rich text formatting" : "השתמשו ב-BBCode לפורמט טקסט עשיר",
"Attach files from your Nextcloud storage" : "צירוף קבצים מאחסון ה-Nextcloud שלך",
"React to posts" : "תגובות לשרשורים",
"Track read/unread threads" : "מעקב אחר שרשורים שנקראו\\לא נקראו",
"BBCode examples:" : "דוגמאות BBCode:",
"Bold text" : "טקסט בולט",
"Use %1$stext%2$s" : "השתמשו ב%1$sטקסט%2$s",
"Italic text" : "טקסט מוטה",
"Underlined text" : "טקסט בקו תחתון",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "הרגישו חופשי להתחיל שרשור חדש או להגיב לשרשורים קיימים. כתיבה מהנה!",
"Welcome to Nextcloud Forums" : "ברוכים הבאים לפורום Nextcloud",
"Forum" : "פורום",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["תגובה {count} חדשה ב-{thread}","{count} תגובות חדשות ב-{thread}","{count} תגובות חדשות ב-{thread}"],
"{user} mentioned you in {thread}" : "אוזכרת על ידי {user}בנושא {thread}",
"Welcome to the forum!" : "ברוכים הבאים לפורום!",
"Deleted user" : "משתמש מחוק",
"A community-driven forum built right into your Nextcloud instance" : "פורום מונחה-קהילה מובנה ישירות להתקנת ה Nextcloud שלכם.",
"Create discussions, share ideas and collaborate with your community directly in Nextcloud.\n\n**⚠️ Early Development Notice:**\nThis app is in early stages of development. While functional, you may encounter bugs or incomplete features. Please report any issues on GitHub and consider backing up your data regularly.\n\n**Key features:**\n- **Thread-based Discussions** - Create and reply to organized discussion threads\n- **Category Organization** - Structure your forum with customizable categories and headers\n- **Rich Text Formatting** - Use BBCode for formatting posts with bold, italic, links, images, code blocks and more\n- **File Attachments** - Attach files from your Nextcloud storage to posts\n- **Post Reactions** - React to posts with emoji reactions\n- **Read/Unread Tracking** - Keep track of which threads you've read\n- **Search** - Find discussions quickly with built-in search\n- **User Profiles** - View user post history and statistics\n- **Role-Based Permissions** - Control access and moderation with flexible roles\n- **Guest Access**: Optional public access for unauthenticated users with configurable permissions\n- **Admin Tools** - Manage categories, roles, BBCodes and forum settings\n- **Moderation Tools** - Pin, lock and manage threads and posts\n\n**Perfect for:**\n- Team discussions and collaboration\n- Community forums\n- Support channels\n- Knowledge bases\n- Project discussions\n- Internal communication\n\nThe forum integrates seamlessly with your Nextcloud instance, using your existing users and groups for authentication and access control." : "צרו דיונים, חלקו רעיונות ושתפו פעולה עם משתמשים ישירות ב-Nextcloud.\n\n**⚠️ הודעה לגבי שלב פיתוח מוקדם:**\nהיישום הזה נמצא בשלבי פיתוח מוקדמים. בעוד היישום שמיש, אתם עלולים להיתקל בבאגים או יכולות לא שלמות. נא לדווח בעיות ב-GitHub ולשקול לגבות את המידע שלכם באופן תכוף.\n\n**יכולות מפתח:**\n- **דיונים מונחי נושאים** - צרו והגיבו לנושאי דיון\n- **ארגון קטגוריות** - צרו מבנה לפורום שלכם עם קטגוריות וכותרות הניתנות להתאמה\n- **פורמט טקסט עשיר** - השתמשו ב-BBCode לפרמוט של פוסטים עם הדגשות, הטיות, קישורים, תמונות, קטעי קוד ועוד\n- **צירוף קבצים** - צרפו קבצים מתוך ה-Nextcloud שלכם לפוסטים\n- **תגובונים לפוסטים** - הגיבו לפוסטים עם תגובוני אמוג'י\n- **מעקב נקרא\\לא נקרא** - עקבו אחרי סטטוס הקריאה של פוסטים\n- **חיפוש** - מצאו דיונים מהר בעזרת חיפוש מובנה\n- **פרופילי משתמשים** - צפו בהיסטוריה וסטטיסטיקות של משתמשים\n- **הרשאות מבוססי תפקיד** - שלטו בגישה ומודרציה עם תפקידים גמישים\n- **גישה לאורחים**: גישת רשות ציבורית למשתמשים לא מחוברים עם הרשאות ניתנות להתאמה\n- **כלי ניהול** - נהלו קטגוריות, תפקידים, BBCode והגדרות פורום\n- **כלי מודרציה** - נעלו, הדביקו, ונהלו נושאים ותגובות\n\n**מושלם עבור:**\n- דיוני צוותים ושיתוף פעולה\n- פורום קהילתי\n- ערוצי תמיכה\n- ניהול ידע\n- דיוני פרוייקטים\n- תקשורת פנימית\n\nהפורום מתממשק באופן ישיר ל-Nextcloud שלכם, ומשתמש במשתמשים קיימים וקבוצות קיימות לניהול גישה והתחברות.",
"Loading …" : "בטעינה…",
"Search" : "חיפוש",
"Home" : "בית",
@@ -48,6 +53,7 @@ OC.L10N.register(
"Users" : "משתמשים",
"Roles" : "תפקידים",
"Categories" : "קטגוריות",
"BBCodes" : "BBCodes",
"Expand" : "הרחבה",
"Collapse" : "קיווץ",
"{bStart}Please note:{bEnd} Attached files will be visible to anyone in the forum, regardless of the file's sharing settings." : "{bStart}נא לשים לב:{bEnd} קבצים מצורפים יהיו זמינים לכל מי שנמצא בפורום, ללא התחשבות בהגדרות השיתוף של הקובץ.",
@@ -73,8 +79,16 @@ OC.L10N.register(
"List" : "רשימה",
"List item within a list" : "פריט רשימה בתוך רשימה",
"List item within a list (alias)" : "פריט רשימה בתוך רשימה (חלופה)",
"BBCode help" : "עזרה עם BBCode",
"Built-in BBCodes" : "BBCode מובנים",
"These BBCodes are available by default." : "ה-BBCode הללו זמינים כברירת מחדל.",
"Custom BBCodes" : "BBCode מותאמים אישית",
"These BBCodes are custom to this forum and configured by administrators." : "ה-BBCodes הללו מותאמים לפורום הזה וניתנים להגדרה על ידי צוות הניהול.",
"Example" : "דוגמה",
"Replacement" : "החלפה",
"Loading custom BBCodes …" : "טוען BBCodes מותאמים …",
"No custom BBCodes configured." : "אין BBCodes מותאמים מוגדרים.",
"Failed to load custom BBCodes" : "כשלון בטעינת BBCodes מותאמים",
"Insert emoji" : "הכנסת אימוג'י",
"Pick file from Nextcloud" : "בחירת קובץ מ-Nextcloud",
"Upload file to Nextcloud" : "העלאת קובץ ל-Nextcloud",
@@ -84,6 +98,7 @@ OC.L10N.register(
"Pick a file to attach" : "בחירת קובץ לצירוף",
"Failed to upload file" : "כשלון בהעלאת קובץ",
"Threads" : "שרשורים",
"Replies" : "תגובות",
"No description available" : "תיאור לא זמין",
"Create category header" : "יצירת כותרת קטגוריות",
"Edit category header" : "עריכת כותרת קטגוריות",
@@ -108,26 +123,129 @@ OC.L10N.register(
"The page you are looking for could not be found." : "העמוד שחיפשתם לא נמצא.",
"Back" : "אחורה",
"Go to home" : "חזרה אל דף הבית",
"Pagination" : "דפדוף",
"First page" : "דף ראשון",
"Previous page" : "דף קודם",
"Next page" : "דף הבא",
"Last page" : "דף אחרון",
"Go to page {page}" : "עבור לדף {page}",
"Edited" : "נערך",
"Quote reply" : "ציטוט בתגובה",
"Edit" : "עריכה",
"Delete" : "מחיקה",
"View edit history" : "צפייה בהיסטוריית עריכה",
"Are you sure you want to delete this post? This action cannot be undone." : "האם אתם בטוחים שתרצו למחוק את הפוסט הזה? הפעולה בלתי ניתנת להפיכה.",
"Unread" : "לא נקרא",
"Edit your reply …" : "ערכו את תגובתכם …",
"Save" : "שמירה",
"Are you sure you want to discard your changes?" : "האם אתם בטוחים שתרצו לבטל את השינויים שלכם?",
"Edit history" : "היסטוריית עריכה",
"Loading history …" : "טוען היסטוריה …",
"This post has no edit history." : "לפוסט הזה אין היסטוריית עריכה.",
"Current version" : "גרסה נוכחית",
"Edited by" : "נערך על ידי",
"Failed to load edit history" : "כשלון בטעינת היסטוריית עריכה",
"Version {index}" : "גרסה {index}",
"Add reaction" : "הוספת תגובון",
"React with {emoji}" : "הגיבו עם {emoji}",
"You reacted with {emoji}" : "הגבת עם {emoji}",
"_You and %n other reacted with {emoji}_::_You and %n others reacted with {emoji}_" : ["את\\ה ועוד %n אחר הגבתם עם {emoji}","את\\ה ועוד %n אחרים הגבתם עם {emoji}","את\\ה ועוד %n אחרים הגבתם עם {emoji}"],
"_%n person reacted with {emoji}_::_%n people reacted with {emoji}_" : ["אדם אחד הגיב עם {emoji}","%n אנשים הגיבו עם {emoji}","%n אנשים הגיבו עם {emoji}"],
"Write your reply …" : "כתבו את תגובתכם …",
"Submit reply" : "שליחת תגובה",
"Are you sure you want to discard your reply?" : "אתם בטוחים שברצונכם לבטל את התגובה?",
"In thread" : "בנושא",
"Thread unavailable" : "נושא לא זמין",
"Pinned thread" : "נושא מודבק",
"Locked thread" : "נושא נעול",
"Uncategorized" : "ללא קטגוריה",
"_%n reply_::_%n replies_" : ["תגובה אחת","%n תגובות","%n תגובות"],
"_%n view_::_%n views_" : ["צפייה אחת","%n צפיות","%n צפיות"],
"Views" : "תצוגות",
"Title" : "כותרת",
"Enter thread title …" : "כתבו כותרת לנושא …",
"Write your thread content …" : "כתבו תוכן לנושא שלכם …",
"Create thread" : "צרו נושא",
"Are you sure you want to discard this thread?" : "אתם בטוחים שברצונכם לבטל את הנושא?",
"Saving draft …" : "הטיוטה נשמרת…",
"Draft saved" : "הטיוטה נשמרה",
"Unsaved changes" : "שינויים שלא נשמרו",
"Back to home" : "חזרה לדף בית",
"Refresh" : "רענון",
"Your bookmarked threads" : "הנושאים בסימניה שלכם",
"Error loading bookmarks" : "שגיאה בטעינת סימניות",
"No bookmarks yet" : "ללא סימניות עדיין",
"Bookmark threads to quickly find them later." : "סמנו נושאים כדי למצוא אותם מהר מאוחר יותר",
"Retry" : "ניסיון חוזר",
"An unexpected error occurred" : "קרתה שגיאה בלתי צפוייה",
"Failed to load bookmarks" : "כשלון בטעינת סימניות",
"No categories yet" : "ללא קטגוריות עדיין",
"Categories will appear here once they are created." : "קטגוריות יופיעו כאן ברגע שייוצרו",
"No categories in this section" : "אין קטגוריות באיזור הזה",
"Category not found" : "קטגוריה לא נמצאה",
"The category you are looking for does not exist or has been removed." : "הקטגוריה שאתם מחפשים לא קיימת או הוסרה.",
"Back to categories" : "חזרה אל קטגוריות",
"New thread" : "נושא חדש",
"Error loading category" : "שגיאה בטעינת קטגוריות",
"No threads yet" : "ללא נושאים עדיין",
"Be the first to start a discussion in this category." : "היו הראשונים שיוצרים דיון בקטגוריה זאת.",
"No category ID or slug provided" : "מזהה קטגוריה לא סופק ",
"Failed to load threads" : "שגיאה בטעינת נושאים",
"Create New Thread" : "צרו נושא חדש",
"In {category}" : "בתוך {category}",
"Creating thread …" : "יוצר נושא …",
"Thread created" : "נושא נוצר",
"Failed to create thread" : "כשלון ביצירת נושא",
"No category specified" : "קטגוריה לא סופקה",
"Error" : "שגיאה",
"First activity" : "פעילות ראשונה",
"Threads ({count})" : "נושאים ({count})",
"Replies ({count})" : "תגובות ({count})",
"No threads" : "ללא נושאים",
"This user has not created any threads yet" : "משתמש זה לא יצר נושאים עדיין",
"No replies" : "ללא תגובות",
"This user has not written any replies yet" : "משתמש זה לא כתב תגובות עדיין",
"Failed to load user profile" : "כשלון בטעינת פרופיל משתמש",
"Enter search query …" : "הכניסו שאילתת חיפוש …",
"Search in threads" : "חפשו בנושאים",
"Search in replies" : "חפשו בתגובות",
"Syntax help" : "עזרה בתחביר",
"Search syntax" : "תחביר חיפוש",
"Match exact phrase" : "התאם מלל מדוייק",
"Both terms required" : "שני הערכים הם חובה",
"Either term matches" : "ערך אחד או אחר מתאים",
"Group conditions with parentheses" : "קבצו תנאים בעזרת סוגריים",
"Exclude term from results" : "השמיטו מתוצאות החיפוש",
"Searching …" : "מתבצע חיפוש…",
"Search Error" : "שגיאה בחיפוש",
"Enter a search query" : "הכניסו שאילתת חיפוש",
"Use the search box above to find threads and replies" : "השתמשו בתיבת החיפוש למעלה כדי למצוא נושאים ותגובות",
"No results found" : "לא נמצאו תוצאות",
"Try different keywords or check your syntax" : "נסו מילות מפתח אחרות או בדקו שוב את תחביר החיפוש שלכם",
"_%n thread found_::_%n threads found_" : ["נמצא נושא אחד","נמצאו %n נושאים","נמצאו %n נושאים"],
"_%n reply found_::_%n replies found_" : ["נמצאה תגובה אחת","נמצאו %n תגובות","נמצאו %n תגובות"],
"Please enter a search query" : "נא להכניס שאילתת חיפוש",
"Please select at least one search scope" : "נא בחרו לפחות איזור חיפוש אחד",
"Failed to search" : "כשלון בחיפוש",
"Thread not found" : "נושא לא נמצא",
"The thread you are looking for does not exist or has been removed." : "הנושא שאתם מנסים לפתוח לא קיים או הוסר",
"Back to {category}" : "חזרה אל {category}",
"Reply" : "תגובה",
"Error loading thread" : "שגיאה בטעינת נושא",
"No replies yet" : "ללא תגובות עדיין",
"Be the first to reply in this thread." : "היו הראשונים שיגיבו לנושא זה",
"by" : "מאת",
"This thread is locked. Only moderators can add replies." : "הנושא הזה נעול. רק מנהלים יכולים להוסיף תגובות.",
"You must be signed in to reply to this thread." : "אתם חייבים להיות מחוברים כדי להגיב לנושא זה.",
"Sign in to reply" : "התחברו כדי להגיב",
"Lock thread" : "נעילת נושא",
"Unlock thread" : "ביטול נעילת נושא",
"Pin thread" : "הדבקת נושא",
"Unpin thread" : "ביטול הדבקת נושא",
"Thread locked" : "הנושא ננעל",
"Thread unlocked" : "בוטלה נעילת הנושא",
"Thread pinned" : "הנושא הודבק",
"Thread unpinned" : "בוטלה הדבקת הנושא",
"Subscribe" : "הרשמה",
"Bookmark" : "סימנייה",
"Edit title" : "עריכת כותרת",

View File

@@ -1,12 +1,12 @@
{ "translations": {
"Admin" : "מנהל",
"Administrator role with full permissions" : "תפקיד מנהל עם הרשאות מלאות",
"Admin" : "אדמין",
"Administrator role with full permissions" : "תפקיד אדמין עם הרשאות מלאות",
"Moderator" : "מפקח",
"Moderator role with elevated permissions" : "תפקיד מפקח עם הרשאות מוגבהות",
"User" : "משתמש",
"Default user role with basic permissions" : "תפקיד משתמש רגיל עם הרשאות בסיסיות",
"Default user role with basic permissions" : "תפקיד משתמש ברירת מחדל עם הרשאות בסיסיות",
"Guest" : "אורח",
"Guest role for unauthenticated users with read-only access" : "תפקיד אורח עם הרשאות גישה לקריאה בלבד",
"Guest role for unauthenticated users with read-only access" : "תפקיד אורח למשתמשים לא-מחוברים עם הרשאות גישה לקריאה בלבד",
"General" : "כללי",
"General discussion categories" : "קטגוריות לדיונים כלליים",
"General discussions" : "דיונים כלליים",
@@ -18,24 +18,29 @@
"Hidden content" : "תוכן מוסתר",
"Spoilers" : "ספוילרים",
"Attachment" : "קובץ מצורף",
"Welcome to Nextcloud Forums" : "ברוכים הבאים לפורום Nextcloud",
"Welcome to the Nextcloud Forums!" : "ברוכים הבאים לפורום של Nextcloud!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "זהו פורום מונחה-קהילה מובנה ישירות ל-Nextcloud שלך. כאן ניתן להתדיין בנושאים שונים, לחלוק רעיונות ולשתף פעולה עם משתמשים אחרים.",
"Features:" : "יכולות:",
"Create and reply to threads" : "יצירה והגבה לשרשורים",
"Organize discussions by categories" : "אירגון דיונים על-פי קטגוריות",
"Use BBCode for rich text formatting" : "השתמשו ב-BBCode לפורמט טקסט עשיר",
"Attach files from your Nextcloud storage" : "צירוף קבצים מאחסון ה-Nextcloud שלך",
"React to posts" : "תגובות לשרשורים",
"Track read/unread threads" : "מעקב אחר שרשורים שנקראו\\לא נקראו",
"BBCode examples:" : "דוגמאות BBCode:",
"Bold text" : "טקסט בולט",
"Use %1$stext%2$s" : "השתמשו ב%1$sטקסט%2$s",
"Italic text" : "טקסט מוטה",
"Underlined text" : "טקסט בקו תחתון",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "הרגישו חופשי להתחיל שרשור חדש או להגיב לשרשורים קיימים. כתיבה מהנה!",
"Welcome to Nextcloud Forums" : "ברוכים הבאים לפורום Nextcloud",
"Forum" : "פורום",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["תגובה {count} חדשה ב-{thread}","{count} תגובות חדשות ב-{thread}","{count} תגובות חדשות ב-{thread}"],
"{user} mentioned you in {thread}" : "אוזכרת על ידי {user}בנושא {thread}",
"Welcome to the forum!" : "ברוכים הבאים לפורום!",
"Deleted user" : "משתמש מחוק",
"A community-driven forum built right into your Nextcloud instance" : "פורום מונחה-קהילה מובנה ישירות להתקנת ה Nextcloud שלכם.",
"Create discussions, share ideas and collaborate with your community directly in Nextcloud.\n\n**⚠️ Early Development Notice:**\nThis app is in early stages of development. While functional, you may encounter bugs or incomplete features. Please report any issues on GitHub and consider backing up your data regularly.\n\n**Key features:**\n- **Thread-based Discussions** - Create and reply to organized discussion threads\n- **Category Organization** - Structure your forum with customizable categories and headers\n- **Rich Text Formatting** - Use BBCode for formatting posts with bold, italic, links, images, code blocks and more\n- **File Attachments** - Attach files from your Nextcloud storage to posts\n- **Post Reactions** - React to posts with emoji reactions\n- **Read/Unread Tracking** - Keep track of which threads you've read\n- **Search** - Find discussions quickly with built-in search\n- **User Profiles** - View user post history and statistics\n- **Role-Based Permissions** - Control access and moderation with flexible roles\n- **Guest Access**: Optional public access for unauthenticated users with configurable permissions\n- **Admin Tools** - Manage categories, roles, BBCodes and forum settings\n- **Moderation Tools** - Pin, lock and manage threads and posts\n\n**Perfect for:**\n- Team discussions and collaboration\n- Community forums\n- Support channels\n- Knowledge bases\n- Project discussions\n- Internal communication\n\nThe forum integrates seamlessly with your Nextcloud instance, using your existing users and groups for authentication and access control." : "צרו דיונים, חלקו רעיונות ושתפו פעולה עם משתמשים ישירות ב-Nextcloud.\n\n**⚠️ הודעה לגבי שלב פיתוח מוקדם:**\nהיישום הזה נמצא בשלבי פיתוח מוקדמים. בעוד היישום שמיש, אתם עלולים להיתקל בבאגים או יכולות לא שלמות. נא לדווח בעיות ב-GitHub ולשקול לגבות את המידע שלכם באופן תכוף.\n\n**יכולות מפתח:**\n- **דיונים מונחי נושאים** - צרו והגיבו לנושאי דיון\n- **ארגון קטגוריות** - צרו מבנה לפורום שלכם עם קטגוריות וכותרות הניתנות להתאמה\n- **פורמט טקסט עשיר** - השתמשו ב-BBCode לפרמוט של פוסטים עם הדגשות, הטיות, קישורים, תמונות, קטעי קוד ועוד\n- **צירוף קבצים** - צרפו קבצים מתוך ה-Nextcloud שלכם לפוסטים\n- **תגובונים לפוסטים** - הגיבו לפוסטים עם תגובוני אמוג'י\n- **מעקב נקרא\\לא נקרא** - עקבו אחרי סטטוס הקריאה של פוסטים\n- **חיפוש** - מצאו דיונים מהר בעזרת חיפוש מובנה\n- **פרופילי משתמשים** - צפו בהיסטוריה וסטטיסטיקות של משתמשים\n- **הרשאות מבוססי תפקיד** - שלטו בגישה ומודרציה עם תפקידים גמישים\n- **גישה לאורחים**: גישת רשות ציבורית למשתמשים לא מחוברים עם הרשאות ניתנות להתאמה\n- **כלי ניהול** - נהלו קטגוריות, תפקידים, BBCode והגדרות פורום\n- **כלי מודרציה** - נעלו, הדביקו, ונהלו נושאים ותגובות\n\n**מושלם עבור:**\n- דיוני צוותים ושיתוף פעולה\n- פורום קהילתי\n- ערוצי תמיכה\n- ניהול ידע\n- דיוני פרוייקטים\n- תקשורת פנימית\n\nהפורום מתממשק באופן ישיר ל-Nextcloud שלכם, ומשתמש במשתמשים קיימים וקבוצות קיימות לניהול גישה והתחברות.",
"Loading …" : "בטעינה…",
"Search" : "חיפוש",
"Home" : "בית",
@@ -46,6 +51,7 @@
"Users" : "משתמשים",
"Roles" : "תפקידים",
"Categories" : "קטגוריות",
"BBCodes" : "BBCodes",
"Expand" : "הרחבה",
"Collapse" : "קיווץ",
"{bStart}Please note:{bEnd} Attached files will be visible to anyone in the forum, regardless of the file's sharing settings." : "{bStart}נא לשים לב:{bEnd} קבצים מצורפים יהיו זמינים לכל מי שנמצא בפורום, ללא התחשבות בהגדרות השיתוף של הקובץ.",
@@ -71,8 +77,16 @@
"List" : "רשימה",
"List item within a list" : "פריט רשימה בתוך רשימה",
"List item within a list (alias)" : "פריט רשימה בתוך רשימה (חלופה)",
"BBCode help" : "עזרה עם BBCode",
"Built-in BBCodes" : "BBCode מובנים",
"These BBCodes are available by default." : "ה-BBCode הללו זמינים כברירת מחדל.",
"Custom BBCodes" : "BBCode מותאמים אישית",
"These BBCodes are custom to this forum and configured by administrators." : "ה-BBCodes הללו מותאמים לפורום הזה וניתנים להגדרה על ידי צוות הניהול.",
"Example" : "דוגמה",
"Replacement" : "החלפה",
"Loading custom BBCodes …" : "טוען BBCodes מותאמים …",
"No custom BBCodes configured." : "אין BBCodes מותאמים מוגדרים.",
"Failed to load custom BBCodes" : "כשלון בטעינת BBCodes מותאמים",
"Insert emoji" : "הכנסת אימוג'י",
"Pick file from Nextcloud" : "בחירת קובץ מ-Nextcloud",
"Upload file to Nextcloud" : "העלאת קובץ ל-Nextcloud",
@@ -82,6 +96,7 @@
"Pick a file to attach" : "בחירת קובץ לצירוף",
"Failed to upload file" : "כשלון בהעלאת קובץ",
"Threads" : "שרשורים",
"Replies" : "תגובות",
"No description available" : "תיאור לא זמין",
"Create category header" : "יצירת כותרת קטגוריות",
"Edit category header" : "עריכת כותרת קטגוריות",
@@ -106,26 +121,129 @@
"The page you are looking for could not be found." : "העמוד שחיפשתם לא נמצא.",
"Back" : "אחורה",
"Go to home" : "חזרה אל דף הבית",
"Pagination" : "דפדוף",
"First page" : "דף ראשון",
"Previous page" : "דף קודם",
"Next page" : "דף הבא",
"Last page" : "דף אחרון",
"Go to page {page}" : "עבור לדף {page}",
"Edited" : "נערך",
"Quote reply" : "ציטוט בתגובה",
"Edit" : "עריכה",
"Delete" : "מחיקה",
"View edit history" : "צפייה בהיסטוריית עריכה",
"Are you sure you want to delete this post? This action cannot be undone." : "האם אתם בטוחים שתרצו למחוק את הפוסט הזה? הפעולה בלתי ניתנת להפיכה.",
"Unread" : "לא נקרא",
"Edit your reply …" : "ערכו את תגובתכם …",
"Save" : "שמירה",
"Are you sure you want to discard your changes?" : "האם אתם בטוחים שתרצו לבטל את השינויים שלכם?",
"Edit history" : "היסטוריית עריכה",
"Loading history …" : "טוען היסטוריה …",
"This post has no edit history." : "לפוסט הזה אין היסטוריית עריכה.",
"Current version" : "גרסה נוכחית",
"Edited by" : "נערך על ידי",
"Failed to load edit history" : "כשלון בטעינת היסטוריית עריכה",
"Version {index}" : "גרסה {index}",
"Add reaction" : "הוספת תגובון",
"React with {emoji}" : "הגיבו עם {emoji}",
"You reacted with {emoji}" : "הגבת עם {emoji}",
"_You and %n other reacted with {emoji}_::_You and %n others reacted with {emoji}_" : ["את\\ה ועוד %n אחר הגבתם עם {emoji}","את\\ה ועוד %n אחרים הגבתם עם {emoji}","את\\ה ועוד %n אחרים הגבתם עם {emoji}"],
"_%n person reacted with {emoji}_::_%n people reacted with {emoji}_" : ["אדם אחד הגיב עם {emoji}","%n אנשים הגיבו עם {emoji}","%n אנשים הגיבו עם {emoji}"],
"Write your reply …" : "כתבו את תגובתכם …",
"Submit reply" : "שליחת תגובה",
"Are you sure you want to discard your reply?" : "אתם בטוחים שברצונכם לבטל את התגובה?",
"In thread" : "בנושא",
"Thread unavailable" : "נושא לא זמין",
"Pinned thread" : "נושא מודבק",
"Locked thread" : "נושא נעול",
"Uncategorized" : "ללא קטגוריה",
"_%n reply_::_%n replies_" : ["תגובה אחת","%n תגובות","%n תגובות"],
"_%n view_::_%n views_" : ["צפייה אחת","%n צפיות","%n צפיות"],
"Views" : "תצוגות",
"Title" : "כותרת",
"Enter thread title …" : "כתבו כותרת לנושא …",
"Write your thread content …" : "כתבו תוכן לנושא שלכם …",
"Create thread" : "צרו נושא",
"Are you sure you want to discard this thread?" : "אתם בטוחים שברצונכם לבטל את הנושא?",
"Saving draft …" : "הטיוטה נשמרת…",
"Draft saved" : "הטיוטה נשמרה",
"Unsaved changes" : "שינויים שלא נשמרו",
"Back to home" : "חזרה לדף בית",
"Refresh" : "רענון",
"Your bookmarked threads" : "הנושאים בסימניה שלכם",
"Error loading bookmarks" : "שגיאה בטעינת סימניות",
"No bookmarks yet" : "ללא סימניות עדיין",
"Bookmark threads to quickly find them later." : "סמנו נושאים כדי למצוא אותם מהר מאוחר יותר",
"Retry" : "ניסיון חוזר",
"An unexpected error occurred" : "קרתה שגיאה בלתי צפוייה",
"Failed to load bookmarks" : "כשלון בטעינת סימניות",
"No categories yet" : "ללא קטגוריות עדיין",
"Categories will appear here once they are created." : "קטגוריות יופיעו כאן ברגע שייוצרו",
"No categories in this section" : "אין קטגוריות באיזור הזה",
"Category not found" : "קטגוריה לא נמצאה",
"The category you are looking for does not exist or has been removed." : "הקטגוריה שאתם מחפשים לא קיימת או הוסרה.",
"Back to categories" : "חזרה אל קטגוריות",
"New thread" : "נושא חדש",
"Error loading category" : "שגיאה בטעינת קטגוריות",
"No threads yet" : "ללא נושאים עדיין",
"Be the first to start a discussion in this category." : "היו הראשונים שיוצרים דיון בקטגוריה זאת.",
"No category ID or slug provided" : "מזהה קטגוריה לא סופק ",
"Failed to load threads" : "שגיאה בטעינת נושאים",
"Create New Thread" : "צרו נושא חדש",
"In {category}" : "בתוך {category}",
"Creating thread …" : "יוצר נושא …",
"Thread created" : "נושא נוצר",
"Failed to create thread" : "כשלון ביצירת נושא",
"No category specified" : "קטגוריה לא סופקה",
"Error" : "שגיאה",
"First activity" : "פעילות ראשונה",
"Threads ({count})" : "נושאים ({count})",
"Replies ({count})" : "תגובות ({count})",
"No threads" : "ללא נושאים",
"This user has not created any threads yet" : "משתמש זה לא יצר נושאים עדיין",
"No replies" : "ללא תגובות",
"This user has not written any replies yet" : "משתמש זה לא כתב תגובות עדיין",
"Failed to load user profile" : "כשלון בטעינת פרופיל משתמש",
"Enter search query …" : "הכניסו שאילתת חיפוש …",
"Search in threads" : "חפשו בנושאים",
"Search in replies" : "חפשו בתגובות",
"Syntax help" : "עזרה בתחביר",
"Search syntax" : "תחביר חיפוש",
"Match exact phrase" : "התאם מלל מדוייק",
"Both terms required" : "שני הערכים הם חובה",
"Either term matches" : "ערך אחד או אחר מתאים",
"Group conditions with parentheses" : "קבצו תנאים בעזרת סוגריים",
"Exclude term from results" : "השמיטו מתוצאות החיפוש",
"Searching …" : "מתבצע חיפוש…",
"Search Error" : "שגיאה בחיפוש",
"Enter a search query" : "הכניסו שאילתת חיפוש",
"Use the search box above to find threads and replies" : "השתמשו בתיבת החיפוש למעלה כדי למצוא נושאים ותגובות",
"No results found" : "לא נמצאו תוצאות",
"Try different keywords or check your syntax" : "נסו מילות מפתח אחרות או בדקו שוב את תחביר החיפוש שלכם",
"_%n thread found_::_%n threads found_" : ["נמצא נושא אחד","נמצאו %n נושאים","נמצאו %n נושאים"],
"_%n reply found_::_%n replies found_" : ["נמצאה תגובה אחת","נמצאו %n תגובות","נמצאו %n תגובות"],
"Please enter a search query" : "נא להכניס שאילתת חיפוש",
"Please select at least one search scope" : "נא בחרו לפחות איזור חיפוש אחד",
"Failed to search" : "כשלון בחיפוש",
"Thread not found" : "נושא לא נמצא",
"The thread you are looking for does not exist or has been removed." : "הנושא שאתם מנסים לפתוח לא קיים או הוסר",
"Back to {category}" : "חזרה אל {category}",
"Reply" : "תגובה",
"Error loading thread" : "שגיאה בטעינת נושא",
"No replies yet" : "ללא תגובות עדיין",
"Be the first to reply in this thread." : "היו הראשונים שיגיבו לנושא זה",
"by" : "מאת",
"This thread is locked. Only moderators can add replies." : "הנושא הזה נעול. רק מנהלים יכולים להוסיף תגובות.",
"You must be signed in to reply to this thread." : "אתם חייבים להיות מחוברים כדי להגיב לנושא זה.",
"Sign in to reply" : "התחברו כדי להגיב",
"Lock thread" : "נעילת נושא",
"Unlock thread" : "ביטול נעילת נושא",
"Pin thread" : "הדבקת נושא",
"Unpin thread" : "ביטול הדבקת נושא",
"Thread locked" : "הנושא ננעל",
"Thread unlocked" : "בוטלה נעילת הנושא",
"Thread pinned" : "הנושא הודבק",
"Thread unpinned" : "בוטלה הדבקת הנושא",
"Subscribe" : "הרשמה",
"Bookmark" : "סימנייה",
"Edit title" : "עריכת כותרת",

View File

@@ -2,6 +2,7 @@ OC.L10N.register(
"forum",
{
"Admin" : "Admin",
"Moderator" : "Moderator",
"User" : "Pengguna",
"Guest" : "Tamu",
"General" : "Umum",
@@ -27,6 +28,7 @@ OC.L10N.register(
"Update" : "Perbarui",
"Move" : "Pindah",
"Page not found" : "Halaman tidak ditemukan",
"Back" : "Kembali",
"Edit" : "Sunting",
"Delete" : "Hapus",
"Save" : "Simpan",
@@ -36,12 +38,14 @@ OC.L10N.register(
"Refresh" : "Muat ulang",
"Retry" : "Ulangi",
"Error" : "Galat",
"Searching …" : "Mencari …",
"Back to {category}" : "Kembali ke {category}",
"by" : "oleh",
"Subscribe" : "Berlangganan",
"Edit title" : "Edit judul",
"Preferences" : "Preferensi",
"Notifications" : "Notifikasi",
"Files" : "File",
"Signature" : "Tanda tangan",
"Enable" : "Aktifkan",
"Disable" : "Nonaktifkan",
@@ -49,6 +53,7 @@ OC.L10N.register(
"Enabled" : "Diaktifkan",
"Name" : "Nama",
"New" : "Baru",
"Last 7 days" : "7 hari terakhir",
"Appearance" : "Tampilan",
"Access control" : "Kontol akses",
"Settings saved" : "Setelan tersimpan",

View File

@@ -1,5 +1,6 @@
{ "translations": {
"Admin" : "Admin",
"Moderator" : "Moderator",
"User" : "Pengguna",
"Guest" : "Tamu",
"General" : "Umum",
@@ -25,6 +26,7 @@
"Update" : "Perbarui",
"Move" : "Pindah",
"Page not found" : "Halaman tidak ditemukan",
"Back" : "Kembali",
"Edit" : "Sunting",
"Delete" : "Hapus",
"Save" : "Simpan",
@@ -34,12 +36,14 @@
"Refresh" : "Muat ulang",
"Retry" : "Ulangi",
"Error" : "Galat",
"Searching …" : "Mencari …",
"Back to {category}" : "Kembali ke {category}",
"by" : "oleh",
"Subscribe" : "Berlangganan",
"Edit title" : "Edit judul",
"Preferences" : "Preferensi",
"Notifications" : "Notifikasi",
"Files" : "File",
"Signature" : "Tanda tangan",
"Enable" : "Aktifkan",
"Disable" : "Nonaktifkan",
@@ -47,6 +51,7 @@
"Enabled" : "Diaktifkan",
"Name" : "Nama",
"New" : "Baru",
"Last 7 days" : "7 hari terakhir",
"Appearance" : "Tampilan",
"Access control" : "Kontol akses",
"Settings saved" : "Setelan tersimpan",

View File

@@ -19,6 +19,7 @@ OC.L10N.register(
"Collapse" : "접기",
"Hello world!" : "Hello world!",
"Code" : "코드",
"Quote" : "ㅇ",
"Font size" : "글꼴 크기",
"List" : "목록",
"Insert emoji" : "이모지 삽입",

View File

@@ -17,6 +17,7 @@
"Collapse" : "접기",
"Hello world!" : "Hello world!",
"Code" : "코드",
"Quote" : "ㅇ",
"Font size" : "글꼴 크기",
"List" : "목록",
"Insert emoji" : "이모지 삽입",

View File

@@ -16,6 +16,7 @@ OC.L10N.register(
"Dashboard" : "Skydelis",
"Users" : "Naudotojai",
"Categories" : "Kategorijos",
"Expand" : "Išskleisti",
"Collapse" : "Suskleisti",
"Hello world!" : "Sveikas, pasauli!",
"Code" : "Kodas",
@@ -56,6 +57,7 @@ OC.L10N.register(
"Edit title" : "Taisyti pavadinimą",
"Move thread" : "Perkelti giją",
"Preferences" : "Nuostatos",
"Notifications" : "Pranešimai",
"Files" : "Failai",
"Browse" : "Naršyti",
"Signature" : "Parašas",

View File

@@ -14,6 +14,7 @@
"Dashboard" : "Skydelis",
"Users" : "Naudotojai",
"Categories" : "Kategorijos",
"Expand" : "Išskleisti",
"Collapse" : "Suskleisti",
"Hello world!" : "Sveikas, pasauli!",
"Code" : "Kodas",
@@ -54,6 +55,7 @@
"Edit title" : "Taisyti pavadinimą",
"Move thread" : "Perkelti giją",
"Preferences" : "Nuostatos",
"Notifications" : "Pranešimai",
"Files" : "Failai",
"Browse" : "Naršyti",
"Signature" : "Parašas",

View File

@@ -20,7 +20,6 @@ OC.L10N.register(
"Hidden content" : "Conteúdo oculto",
"Spoilers" : "Spoilers",
"Attachment" : "Anexo",
"Welcome to Nextcloud Forums" : "Bem-vindo ao Nextcloud Fórums",
"Welcome to the Nextcloud Forums!" : "Bem-vindo ao Nextcloud Fórums!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Este é um fórum comunitário integrado à sua instância Nextcloud. Aqui você pode discutir tópicos, compartilhar ideias e colaborar com outros usuários.",
"Features:" : "Características:",
@@ -36,6 +35,7 @@ OC.L10N.register(
"Italic text" : "Texto em itálico",
"Underlined text" : "Texto sublinhado",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Sinta-se à vontade para iniciar uma nova discussão ou responder a fios existentes. Boas postagens!",
"Welcome to Nextcloud Forums" : "Bem-vindo ao Nextcloud Fórums",
"Forum" : "Fórum",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} nova resposta em {thread}","{count} de novas respostas em {thread}","{count} novas respostas em {thread}"],
"{user} mentioned you in {thread}" : "{user} mencionou você em {thread}",
@@ -282,6 +282,8 @@ OC.L10N.register(
"Configure how you receive notifications" : "Configure como você recebe notificações",
"Auto-subscribe to threads I create" : "Inscreva-me automaticamente nos fios que eu criar",
"When enabled, you will automatically receive notifications for replies to threads you create" : "Quando ativado, você receberá automaticamente notificações sobre respostas aos fios que criar",
"Auto-subscribe to threads I reply to" : "Inscrever-se automaticamente nos fios aos quais respondo",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "Quando ativado, você receberá automaticamente notificações sobre novas respostas nos fios em que você respondeu.",
"Files" : "Arquivos",
"Configure file upload settings" : "Configurar as definições de envio de arquivos",
"Upload directory" : "Diretório de upload",

View File

@@ -18,7 +18,6 @@
"Hidden content" : "Conteúdo oculto",
"Spoilers" : "Spoilers",
"Attachment" : "Anexo",
"Welcome to Nextcloud Forums" : "Bem-vindo ao Nextcloud Fórums",
"Welcome to the Nextcloud Forums!" : "Bem-vindo ao Nextcloud Fórums!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Este é um fórum comunitário integrado à sua instância Nextcloud. Aqui você pode discutir tópicos, compartilhar ideias e colaborar com outros usuários.",
"Features:" : "Características:",
@@ -34,6 +33,7 @@
"Italic text" : "Texto em itálico",
"Underlined text" : "Texto sublinhado",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Sinta-se à vontade para iniciar uma nova discussão ou responder a fios existentes. Boas postagens!",
"Welcome to Nextcloud Forums" : "Bem-vindo ao Nextcloud Fórums",
"Forum" : "Fórum",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} nova resposta em {thread}","{count} de novas respostas em {thread}","{count} novas respostas em {thread}"],
"{user} mentioned you in {thread}" : "{user} mencionou você em {thread}",
@@ -280,6 +280,8 @@
"Configure how you receive notifications" : "Configure como você recebe notificações",
"Auto-subscribe to threads I create" : "Inscreva-me automaticamente nos fios que eu criar",
"When enabled, you will automatically receive notifications for replies to threads you create" : "Quando ativado, você receberá automaticamente notificações sobre respostas aos fios que criar",
"Auto-subscribe to threads I reply to" : "Inscrever-se automaticamente nos fios aos quais respondo",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "Quando ativado, você receberá automaticamente notificações sobre novas respostas nos fios em que você respondeu.",
"Files" : "Arquivos",
"Configure file upload settings" : "Configurar as definições de envio de arquivos",
"Upload directory" : "Diretório de upload",

View File

@@ -20,10 +20,10 @@ OC.L10N.register(
"Hidden content" : "Conteúdo oculto",
"Spoilers" : "Contém spoilers",
"Attachment" : "Anexo",
"Welcome to Nextcloud Forums" : "Bem-vindo aos Fóruns Nextcloud",
"Welcome to the Nextcloud Forums!" : "Bem-vindo aos Fóruns do Nextcloud!",
"Bold text" : "Texto a negrito",
"Underlined text" : "Texto sublinhado",
"Welcome to Nextcloud Forums" : "Bem-vindo aos Fóruns Nextcloud",
"Forum" : "Fórum",
"Create discussions, share ideas and collaborate with your community directly in Nextcloud.\n\n**⚠️ Early Development Notice:**\nThis app is in early stages of development. While functional, you may encounter bugs or incomplete features. Please report any issues on GitHub and consider backing up your data regularly.\n\n**Key features:**\n- **Thread-based Discussions** - Create and reply to organized discussion threads\n- **Category Organization** - Structure your forum with customizable categories and headers\n- **Rich Text Formatting** - Use BBCode for formatting posts with bold, italic, links, images, code blocks and more\n- **File Attachments** - Attach files from your Nextcloud storage to posts\n- **Post Reactions** - React to posts with emoji reactions\n- **Read/Unread Tracking** - Keep track of which threads you've read\n- **Search** - Find discussions quickly with built-in search\n- **User Profiles** - View user post history and statistics\n- **Role-Based Permissions** - Control access and moderation with flexible roles\n- **Guest Access**: Optional public access for unauthenticated users with configurable permissions\n- **Admin Tools** - Manage categories, roles, BBCodes and forum settings\n- **Moderation Tools** - Pin, lock and manage threads and posts\n\n**Perfect for:**\n- Team discussions and collaboration\n- Community forums\n- Support channels\n- Knowledge bases\n- Project discussions\n- Internal communication\n\nThe forum integrates seamlessly with your Nextcloud instance, using your existing users and groups for authentication and access control." : "Crie discussões, partilhe ideias e colabore com a sua comunidade diretamente no Nextcloud.\n\n**⚠️ Aviso de Desenvolvimento Inicial:**\nEsta aplicação está em fase inicial de desenvolvimento. Embora funcional, pode encontrar bugs ou funcionalidades incompletas. Por favor, comunique quaisquer problemas no GitHub e considere fazer cópias de segurança dos seus dados regularmente.\n\n**Principais características:**\n- **Discussões sobre tópicos** - Crie e responda a tópicos de discussão organizados\n- **Organização por categorias** - Estruture o seu fórum com categorias e cabeçalhos personalizáveis\n- **Formatação de texto avançada** - Utilize o BBCode para formatar as publicações com negrito, itálico, links, imagens, blocos de código e muito mais\n- **Anexos de ficheiros** - Anexe ficheiros do seu armazenamento Nextcloud às publicações\n- **Reações às publicações** - Reagir às publicações com emojis\n- **Controlo de leitura/não leitura** - Acompanhe quais os tópicos que já leu\n- **Pesquisa** - Encontre discussões rapidamente com a pesquisa integrada\n- **Perfis de utilizador** - Visualize o histórico de publicações e as estatísticas do utilizador\n- **Permissões baseadas em funções** - Controle o acesso e a moderação com funções flexíveis\n- **Acesso de convidado**: Acesso público opcional para utilizadores não autenticados com permissões configuráveis\n- **Ferramentas de administração** - Gerir categorias, funções, BBCodes e definições do fórum\n- **Ferramentas de moderação** - Fixar, bloquear e gerir tópicos e publicações\n\n**Ideal para:**\n- Discussões e colaboração em equipa\n- Fóruns da comunidade\n- Canais de suporte\n- Bases de conhecimento\n- Discussões sobre projetos\n- Comunicação interna\n\nO fórum integra-se perfeitamente na sua instância do Nextcloud, utilizando os seus utilizadores e grupos existentes para autenticação e controlo de acesso.",
"Search" : "Pesquisa sobre",

View File

@@ -18,10 +18,10 @@
"Hidden content" : "Conteúdo oculto",
"Spoilers" : "Contém spoilers",
"Attachment" : "Anexo",
"Welcome to Nextcloud Forums" : "Bem-vindo aos Fóruns Nextcloud",
"Welcome to the Nextcloud Forums!" : "Bem-vindo aos Fóruns do Nextcloud!",
"Bold text" : "Texto a negrito",
"Underlined text" : "Texto sublinhado",
"Welcome to Nextcloud Forums" : "Bem-vindo aos Fóruns Nextcloud",
"Forum" : "Fórum",
"Create discussions, share ideas and collaborate with your community directly in Nextcloud.\n\n**⚠️ Early Development Notice:**\nThis app is in early stages of development. While functional, you may encounter bugs or incomplete features. Please report any issues on GitHub and consider backing up your data regularly.\n\n**Key features:**\n- **Thread-based Discussions** - Create and reply to organized discussion threads\n- **Category Organization** - Structure your forum with customizable categories and headers\n- **Rich Text Formatting** - Use BBCode for formatting posts with bold, italic, links, images, code blocks and more\n- **File Attachments** - Attach files from your Nextcloud storage to posts\n- **Post Reactions** - React to posts with emoji reactions\n- **Read/Unread Tracking** - Keep track of which threads you've read\n- **Search** - Find discussions quickly with built-in search\n- **User Profiles** - View user post history and statistics\n- **Role-Based Permissions** - Control access and moderation with flexible roles\n- **Guest Access**: Optional public access for unauthenticated users with configurable permissions\n- **Admin Tools** - Manage categories, roles, BBCodes and forum settings\n- **Moderation Tools** - Pin, lock and manage threads and posts\n\n**Perfect for:**\n- Team discussions and collaboration\n- Community forums\n- Support channels\n- Knowledge bases\n- Project discussions\n- Internal communication\n\nThe forum integrates seamlessly with your Nextcloud instance, using your existing users and groups for authentication and access control." : "Crie discussões, partilhe ideias e colabore com a sua comunidade diretamente no Nextcloud.\n\n**⚠️ Aviso de Desenvolvimento Inicial:**\nEsta aplicação está em fase inicial de desenvolvimento. Embora funcional, pode encontrar bugs ou funcionalidades incompletas. Por favor, comunique quaisquer problemas no GitHub e considere fazer cópias de segurança dos seus dados regularmente.\n\n**Principais características:**\n- **Discussões sobre tópicos** - Crie e responda a tópicos de discussão organizados\n- **Organização por categorias** - Estruture o seu fórum com categorias e cabeçalhos personalizáveis\n- **Formatação de texto avançada** - Utilize o BBCode para formatar as publicações com negrito, itálico, links, imagens, blocos de código e muito mais\n- **Anexos de ficheiros** - Anexe ficheiros do seu armazenamento Nextcloud às publicações\n- **Reações às publicações** - Reagir às publicações com emojis\n- **Controlo de leitura/não leitura** - Acompanhe quais os tópicos que já leu\n- **Pesquisa** - Encontre discussões rapidamente com a pesquisa integrada\n- **Perfis de utilizador** - Visualize o histórico de publicações e as estatísticas do utilizador\n- **Permissões baseadas em funções** - Controle o acesso e a moderação com funções flexíveis\n- **Acesso de convidado**: Acesso público opcional para utilizadores não autenticados com permissões configuráveis\n- **Ferramentas de administração** - Gerir categorias, funções, BBCodes e definições do fórum\n- **Ferramentas de moderação** - Fixar, bloquear e gerir tópicos e publicações\n\n**Ideal para:**\n- Discussões e colaboração em equipa\n- Fóruns da comunidade\n- Canais de suporte\n- Bases de conhecimento\n- Discussões sobre projetos\n- Comunicação interna\n\nO fórum integra-se perfeitamente na sua instância do Nextcloud, utilizando os seus utilizadores e grupos existentes para autenticação e controlo de acesso.",
"Search" : "Pesquisa sobre",

View File

@@ -20,7 +20,6 @@ OC.L10N.register(
"Hidden content" : "Скрытый контент",
"Spoilers" : "Спойлеры",
"Attachment" : "Вложение",
"Welcome to Nextcloud Forums" : "Добро пожаловать на форумы Nextcloud",
"Welcome to the Nextcloud Forums!" : "Добро пожаловать на форумы Nextcloud!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Это форум, созданный сообществом и встроенный прямо в ваш экземпляр Nextcloud. Здесь вы можете обсуждать темы, делиться идеями и сотрудничать с другими пользователями.",
"Features:" : "Функции:",
@@ -36,6 +35,7 @@ OC.L10N.register(
"Italic text" : "Курсивный текст",
"Underlined text" : "Подчеркнутый текст",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Не стесняйтесь начинать новое обсуждение или отвечать на уже существующие. Удачной публикации!",
"Welcome to Nextcloud Forums" : "Добро пожаловать на форумы Nextcloud",
"Forum" : "Форум",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} новый ответ в {thread}","{count} новых ответов в {thread}","{count} новых ответов в {thread}","{count} новых ответов в {thread}"],
"Welcome to the forum!" : "Добро пожаловать на форум!",

View File

@@ -18,7 +18,6 @@
"Hidden content" : "Скрытый контент",
"Spoilers" : "Спойлеры",
"Attachment" : "Вложение",
"Welcome to Nextcloud Forums" : "Добро пожаловать на форумы Nextcloud",
"Welcome to the Nextcloud Forums!" : "Добро пожаловать на форумы Nextcloud!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Это форум, созданный сообществом и встроенный прямо в ваш экземпляр Nextcloud. Здесь вы можете обсуждать темы, делиться идеями и сотрудничать с другими пользователями.",
"Features:" : "Функции:",
@@ -34,6 +33,7 @@
"Italic text" : "Курсивный текст",
"Underlined text" : "Подчеркнутый текст",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Не стесняйтесь начинать новое обсуждение или отвечать на уже существующие. Удачной публикации!",
"Welcome to Nextcloud Forums" : "Добро пожаловать на форумы Nextcloud",
"Forum" : "Форум",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} новый ответ в {thread}","{count} новых ответов в {thread}","{count} новых ответов в {thread}","{count} новых ответов в {thread}"],
"Welcome to the forum!" : "Добро пожаловать на форум!",

View File

@@ -20,7 +20,6 @@ OC.L10N.register(
"Hidden content" : "Maudhui yaliyofichika",
"Spoilers" : "Waharibifu",
"Attachment" : "Kiambatisho",
"Welcome to Nextcloud Forums" : "Karibu kwenye jukwaa la Nextcloud ",
"Welcome to the Nextcloud Forums!" : "Karibu kwenye majukwaa ya Nextcloud! ",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Hili ni jukwaa linaloendeshwa na jamii lililojengwa ndani ya mfano wako wa Nextcloud. Hapa unaweza kujadili mada, kushiriki mawazo na kushirikiana na watumiaji wengine.",
"Features:" : "Sifa:",
@@ -36,6 +35,7 @@ OC.L10N.register(
"Italic text" : "Maandishi ya italiki",
"Underlined text" : "Maandishi yaliyopigiwa mstari",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Jisikie huru kuanzisha mjadala mpya au kujibu mazungumzo yaliyopo. Furaha ya kuchapisha!",
"Welcome to Nextcloud Forums" : "Karibu kwenye jukwaa la Nextcloud ",
"Forum" : "Jukwaa",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} new reply in {thread}","{count} majibu mapya ndani {thread}"],
"{user} mentioned you in {thread}" : "{user} amekutaja katika {thread}",

View File

@@ -18,7 +18,6 @@
"Hidden content" : "Maudhui yaliyofichika",
"Spoilers" : "Waharibifu",
"Attachment" : "Kiambatisho",
"Welcome to Nextcloud Forums" : "Karibu kwenye jukwaa la Nextcloud ",
"Welcome to the Nextcloud Forums!" : "Karibu kwenye majukwaa ya Nextcloud! ",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Hili ni jukwaa linaloendeshwa na jamii lililojengwa ndani ya mfano wako wa Nextcloud. Hapa unaweza kujadili mada, kushiriki mawazo na kushirikiana na watumiaji wengine.",
"Features:" : "Sifa:",
@@ -34,6 +33,7 @@
"Italic text" : "Maandishi ya italiki",
"Underlined text" : "Maandishi yaliyopigiwa mstari",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Jisikie huru kuanzisha mjadala mpya au kujibu mazungumzo yaliyopo. Furaha ya kuchapisha!",
"Welcome to Nextcloud Forums" : "Karibu kwenye jukwaa la Nextcloud ",
"Forum" : "Jukwaa",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{count} new reply in {thread}","{count} majibu mapya ndani {thread}"],
"{user} mentioned you in {thread}" : "{user} amekutaja katika {thread}",

View File

@@ -20,7 +20,6 @@ OC.L10N.register(
"Hidden content" : "Gizli içerik",
"Spoilers" : "Alıntılar",
"Attachment" : "Ek dosya",
"Welcome to Nextcloud Forums" : "Nextcloud forumuna hoş geldiniz",
"Welcome to the Nextcloud Forums!" : "Nextcloud forumuna hoş geldiniz!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Bu, doğrudan Nextcloud kopyanızda bulunan topluluk odaklı bir forumdur. Burada konuları tartışabilir, fikirleri paylaşabilir ve diğer kullanıcılarla iş birliği yapabilirsiniz.",
"Features:" : "Özellikler:",
@@ -36,6 +35,7 @@ OC.L10N.register(
"Italic text" : "Yatık yazı",
"Underlined text" : "Altı çizili yazı",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Yeni bir tartışma başlatmaktan veya var olan konulara yanıt vermekten çekinmeyin. Mutlu yazışmalar!",
"Welcome to Nextcloud Forums" : "Nextcloud forumuna hoş geldiniz",
"Forum" : "Forum",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{thread} konusunda {count} yeni yanıt","{thread} konusunda {count} yeni yanıt"],
"{user} mentioned you in {thread}" : "{user} sizi {thread} konusunda andı",
@@ -282,6 +282,8 @@ OC.L10N.register(
"Configure how you receive notifications" : "Bildirimleri nasıl almak istediğinizi ayarlayın",
"Auto-subscribe to threads I create" : "Oluşturduğum konulara otomatik olarak abone olayım",
"When enabled, you will automatically receive notifications for replies to threads you create" : "Açıldığında, oluşturduğunuz konulara verilen yanıtlar için bildirimleri otomatik olarak alırsınız",
"Auto-subscribe to threads I reply to" : "Yanıtladığım konulara otomatik olarak abone olayım",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "Açıldığında, yanıtladığınız konulara verilen yanıtlar için bildirimleri otomatik olarak alırsınız",
"Files" : "Dosyalar",
"Configure file upload settings" : "Dosya yükleme ayarlarını yapılandırın",
"Upload directory" : "Yükleme klasörü",

View File

@@ -18,7 +18,6 @@
"Hidden content" : "Gizli içerik",
"Spoilers" : "Alıntılar",
"Attachment" : "Ek dosya",
"Welcome to Nextcloud Forums" : "Nextcloud forumuna hoş geldiniz",
"Welcome to the Nextcloud Forums!" : "Nextcloud forumuna hoş geldiniz!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "Bu, doğrudan Nextcloud kopyanızda bulunan topluluk odaklı bir forumdur. Burada konuları tartışabilir, fikirleri paylaşabilir ve diğer kullanıcılarla iş birliği yapabilirsiniz.",
"Features:" : "Özellikler:",
@@ -34,6 +33,7 @@
"Italic text" : "Yatık yazı",
"Underlined text" : "Altı çizili yazı",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "Yeni bir tartışma başlatmaktan veya var olan konulara yanıt vermekten çekinmeyin. Mutlu yazışmalar!",
"Welcome to Nextcloud Forums" : "Nextcloud forumuna hoş geldiniz",
"Forum" : "Forum",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{thread} konusunda {count} yeni yanıt","{thread} konusunda {count} yeni yanıt"],
"{user} mentioned you in {thread}" : "{user} sizi {thread} konusunda andı",
@@ -280,6 +280,8 @@
"Configure how you receive notifications" : "Bildirimleri nasıl almak istediğinizi ayarlayın",
"Auto-subscribe to threads I create" : "Oluşturduğum konulara otomatik olarak abone olayım",
"When enabled, you will automatically receive notifications for replies to threads you create" : "Açıldığında, oluşturduğunuz konulara verilen yanıtlar için bildirimleri otomatik olarak alırsınız",
"Auto-subscribe to threads I reply to" : "Yanıtladığım konulara otomatik olarak abone olayım",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "Açıldığında, yanıtladığınız konulara verilen yanıtlar için bildirimleri otomatik olarak alırsınız",
"Files" : "Dosyalar",
"Configure file upload settings" : "Dosya yükleme ayarlarını yapılandırın",
"Upload directory" : "Yükleme klasörü",

View File

@@ -20,7 +20,6 @@ OC.L10N.register(
"Hidden content" : "يوشۇرۇن مەزمۇن",
"Spoilers" : "بۇزغۇنچىلار",
"Attachment" : "قوشۇمچە",
"Welcome to Nextcloud Forums" : "Nextcloud مۇنبىرىگە خۇش كەپسىز",
"Welcome to the Nextcloud Forums!" : "Nextcloud مۇنبىرىگە خۇش كەپسىز!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "بۇ Nextcloud ئۈلگىڭىزگە قۇرۇلغان مەھەللە باشلامچىلىق قىلغان مۇنبەر. بۇ يەردە سىز تېمىلارنى مۇزاكىرە قىلالايسىز، پىكىر ئورتاقلىشالايسىز ۋە باشقا ئىشلەتكۈچىلەر بىلەن ھەمكارلىشالايسىز.",
"Features:" : "ئالاھىدىلىكلىرى:",
@@ -36,6 +35,7 @@ OC.L10N.register(
"Italic text" : "قىيسىق تېكىست",
"Underlined text" : "ئاستى سىزىقلىق تېكىست",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "يېڭى مۇنازىرە باشلىسىڭىز ياكى مەۋجۇت تېمىلارغا جاۋاب قايتۇرسىڭىز بولىدۇ. خەيرلىك يوللاڭ!",
"Welcome to Nextcloud Forums" : "Nextcloud مۇنبىرىگە خۇش كەپسىز",
"Forum" : "مۇنبەر",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{thread} دا {count} يېڭى جاۋاپ","{thread} دا {count} يېڭى جاۋاپ"],
"{user} mentioned you in {thread}" : "{user} سىزنى {thread} دا تىلغا ئالدى",

View File

@@ -18,7 +18,6 @@
"Hidden content" : "يوشۇرۇن مەزمۇن",
"Spoilers" : "بۇزغۇنچىلار",
"Attachment" : "قوشۇمچە",
"Welcome to Nextcloud Forums" : "Nextcloud مۇنبىرىگە خۇش كەپسىز",
"Welcome to the Nextcloud Forums!" : "Nextcloud مۇنبىرىگە خۇش كەپسىز!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "بۇ Nextcloud ئۈلگىڭىزگە قۇرۇلغان مەھەللە باشلامچىلىق قىلغان مۇنبەر. بۇ يەردە سىز تېمىلارنى مۇزاكىرە قىلالايسىز، پىكىر ئورتاقلىشالايسىز ۋە باشقا ئىشلەتكۈچىلەر بىلەن ھەمكارلىشالايسىز.",
"Features:" : "ئالاھىدىلىكلىرى:",
@@ -34,6 +33,7 @@
"Italic text" : "قىيسىق تېكىست",
"Underlined text" : "ئاستى سىزىقلىق تېكىست",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "يېڭى مۇنازىرە باشلىسىڭىز ياكى مەۋجۇت تېمىلارغا جاۋاب قايتۇرسىڭىز بولىدۇ. خەيرلىك يوللاڭ!",
"Welcome to Nextcloud Forums" : "Nextcloud مۇنبىرىگە خۇش كەپسىز",
"Forum" : "مۇنبەر",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{thread} دا {count} يېڭى جاۋاپ","{thread} دا {count} يېڭى جاۋاپ"],
"{user} mentioned you in {thread}" : "{user} سىزنى {thread} دا تىلغا ئالدى",

View File

@@ -20,7 +20,6 @@ OC.L10N.register(
"Hidden content" : "隱藏的內容",
"Spoilers" : "劇透",
"Attachment" : "附件",
"Welcome to Nextcloud Forums" : "歡迎來到 Nextcloud 論壇",
"Welcome to the Nextcloud Forums!" : "歡迎來到 Nextcloud 論壇!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "這是一個直接建置在你 Nextcloud 實例之中的社群主導論壇。你可以在這裡討論各種主題、分享意見,並與其他用戶協作。",
"Features:" : "功能:",
@@ -36,6 +35,7 @@ OC.L10N.register(
"Italic text" : "斜體文字",
"Underlined text" : "下面畫線的文字",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "歡迎隨時開啟新討論或回覆現有主題,玩得開心!",
"Welcome to Nextcloud Forums" : "歡迎來到 Nextcloud 論壇",
"Forum" : "論壇",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{thread} 中有 {count} 個新回覆"],
"{user} mentioned you in {thread}" : "{user} 在 {thread} 中提及您",
@@ -282,6 +282,8 @@ OC.L10N.register(
"Configure how you receive notifications" : "設定你接收通知的方式",
"Auto-subscribe to threads I create" : "自動訂閱由我建立的主題",
"When enabled, you will automatically receive notifications for replies to threads you create" : "啟用後,你會自動收到針對你所建立主題之回覆通知。",
"Auto-subscribe to threads I reply to" : "自動訂閱我回覆的討論串",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "啟用後,您將會自動收到您回覆過的討論串中的新回覆通知",
"Files" : "檔案",
"Configure file upload settings" : "配置檔案上傳設定",
"Upload directory" : "上載目錄",

View File

@@ -18,7 +18,6 @@
"Hidden content" : "隱藏的內容",
"Spoilers" : "劇透",
"Attachment" : "附件",
"Welcome to Nextcloud Forums" : "歡迎來到 Nextcloud 論壇",
"Welcome to the Nextcloud Forums!" : "歡迎來到 Nextcloud 論壇!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "這是一個直接建置在你 Nextcloud 實例之中的社群主導論壇。你可以在這裡討論各種主題、分享意見,並與其他用戶協作。",
"Features:" : "功能:",
@@ -34,6 +33,7 @@
"Italic text" : "斜體文字",
"Underlined text" : "下面畫線的文字",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "歡迎隨時開啟新討論或回覆現有主題,玩得開心!",
"Welcome to Nextcloud Forums" : "歡迎來到 Nextcloud 論壇",
"Forum" : "論壇",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{thread} 中有 {count} 個新回覆"],
"{user} mentioned you in {thread}" : "{user} 在 {thread} 中提及您",
@@ -280,6 +280,8 @@
"Configure how you receive notifications" : "設定你接收通知的方式",
"Auto-subscribe to threads I create" : "自動訂閱由我建立的主題",
"When enabled, you will automatically receive notifications for replies to threads you create" : "啟用後,你會自動收到針對你所建立主題之回覆通知。",
"Auto-subscribe to threads I reply to" : "自動訂閱我回覆的討論串",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "啟用後,您將會自動收到您回覆過的討論串中的新回覆通知",
"Files" : "檔案",
"Configure file upload settings" : "配置檔案上傳設定",
"Upload directory" : "上載目錄",

View File

@@ -20,7 +20,6 @@ OC.L10N.register(
"Hidden content" : "隱藏內容",
"Spoilers" : "劇透",
"Attachment" : "附件",
"Welcome to Nextcloud Forums" : "歡迎使用 Nextcloud 論壇",
"Welcome to the Nextcloud Forums!" : "歡迎使用 Nextcloud 論壇!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "這是內建於您的 Nextcloud 站台中的社群驅動論壇。在此您可以討論主題、分享想法,並與其他使用者協作。",
"Features:" : "功能:",
@@ -36,6 +35,7 @@ OC.L10N.register(
"Italic text" : "斜體文字",
"Underlined text" : "有底線的文字",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "歡迎隨時開啟新討論或回覆現有主題。祝您發文愉快!",
"Welcome to Nextcloud Forums" : "歡迎使用 Nextcloud 論壇",
"Forum" : "論壇",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{thread} 中有 {count} 個新回覆"],
"{user} mentioned you in {thread}" : "{user} 在 {thread} 中提及您",
@@ -282,6 +282,8 @@ OC.L10N.register(
"Configure how you receive notifications" : "設定您要如何接收通知",
"Auto-subscribe to threads I create" : "自動訂閱我建立的討論串",
"When enabled, you will automatically receive notifications for replies to threads you create" : "啟用後,您會自動收到您所建立的討論串回覆的通知",
"Auto-subscribe to threads I reply to" : "自動訂閱我回覆的討論串",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "啟用後,您將會自動收到您回覆過的討論串中的新回覆通知",
"Files" : "檔案",
"Configure file upload settings" : "設定檔案上傳設定",
"Upload directory" : "上傳目錄",

View File

@@ -18,7 +18,6 @@
"Hidden content" : "隱藏內容",
"Spoilers" : "劇透",
"Attachment" : "附件",
"Welcome to Nextcloud Forums" : "歡迎使用 Nextcloud 論壇",
"Welcome to the Nextcloud Forums!" : "歡迎使用 Nextcloud 論壇!",
"This is a community-driven forum built right into your Nextcloud instance. Here you can discuss topics, share ideas and collaborate with other users." : "這是內建於您的 Nextcloud 站台中的社群驅動論壇。在此您可以討論主題、分享想法,並與其他使用者協作。",
"Features:" : "功能:",
@@ -34,6 +33,7 @@
"Italic text" : "斜體文字",
"Underlined text" : "有底線的文字",
"Feel free to start a new discussion or reply to existing threads. Happy posting!" : "歡迎隨時開啟新討論或回覆現有主題。祝您發文愉快!",
"Welcome to Nextcloud Forums" : "歡迎使用 Nextcloud 論壇",
"Forum" : "論壇",
"_{count} new reply in {thread}_::_{count} new replies in {thread}_" : ["{thread} 中有 {count} 個新回覆"],
"{user} mentioned you in {thread}" : "{user} 在 {thread} 中提及您",
@@ -280,6 +280,8 @@
"Configure how you receive notifications" : "設定您要如何接收通知",
"Auto-subscribe to threads I create" : "自動訂閱我建立的討論串",
"When enabled, you will automatically receive notifications for replies to threads you create" : "啟用後,您會自動收到您所建立的討論串回覆的通知",
"Auto-subscribe to threads I reply to" : "自動訂閱我回覆的討論串",
"When enabled, you will automatically receive notifications for new replies in threads you have replied to" : "啟用後,您將會自動收到您回覆過的討論串中的新回覆通知",
"Files" : "檔案",
"Configure file upload settings" : "設定檔案上傳設定",
"Upload directory" : "上傳目錄",

View File

@@ -87,7 +87,8 @@ class RepairSeeds extends Command {
}
};
SeedHelper::seedAll($migrationOutput);
// Pass throwOnError=true so users get proper error feedback
SeedHelper::seedAll($migrationOutput, true);
$output->writeln('');
$output->writeln('<info>Forum data repair/seed completed successfully!</info>');

View File

@@ -192,6 +192,7 @@ class CategoryController extends OCSController {
* Update a category
*
* @param int $id Category ID
* @param int|null $headerId Category header ID
* @param string|null $name Category name
* @param string|null $description Category description
* @param string|null $slug Category slug
@@ -203,10 +204,13 @@ class CategoryController extends OCSController {
#[NoAdminRequired]
#[RequirePermission('canEditCategories')]
#[ApiRoute(verb: 'PUT', url: '/api/categories/{id}')]
public function update(int $id, ?string $name = null, ?string $description = null, ?string $slug = null, ?int $sortOrder = null): DataResponse {
public function update(int $id, ?int $headerId = null, ?string $name = null, ?string $description = null, ?string $slug = null, ?int $sortOrder = null): DataResponse {
try {
$category = $this->categoryMapper->find($id);
if ($headerId !== null) {
$category->setHeaderId($headerId);
}
if ($name !== null) {
$category->setName($name);
}

View File

@@ -15,11 +15,13 @@ use OCA\Forum\Db\PostMapper;
use OCA\Forum\Db\ReactionMapper;
use OCA\Forum\Db\ReadMarkerMapper;
use OCA\Forum\Db\ThreadMapper;
use OCA\Forum\Db\ThreadSubscriptionMapper;
use OCA\Forum\Service\BBCodeService;
use OCA\Forum\Service\NotificationService;
use OCA\Forum\Service\PermissionService;
use OCA\Forum\Service\PostEnrichmentService;
use OCA\Forum\Service\PostHistoryService;
use OCA\Forum\Service\UserPreferencesService;
use OCA\Forum\Service\UserService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
@@ -49,6 +51,8 @@ class PostController extends OCSController {
private PostEnrichmentService $postEnrichmentService,
private PostHistoryService $postHistoryService,
private UserService $userService,
private UserPreferencesService $userPreferencesService,
private ThreadSubscriptionMapper $threadSubscriptionMapper,
private IUserSession $userSession,
private LoggerInterface $logger,
) {
@@ -400,6 +404,21 @@ class PostController extends OCSController {
// Don't fail the request if mention notification sending fails
}
// Auto-subscribe the user to the thread if preference is enabled and not already subscribed
try {
$autoSubscribe = $this->userPreferencesService->getPreference(
$user->getUID(),
UserPreferencesService::PREF_AUTO_SUBSCRIBE_REPLIED_THREADS
);
if ($autoSubscribe && !$this->threadSubscriptionMapper->isUserSubscribed($user->getUID(), $threadId)) {
$this->threadSubscriptionMapper->subscribe($user->getUID(), $threadId);
}
} catch (\Exception $e) {
$this->logger->warning('Failed to auto-subscribe user to thread: ' . $e->getMessage());
// Don't fail the request if auto-subscribe fails
}
return new DataResponse($this->postEnrichmentService->enrichPost($createdPost), Http::STATUS_CREATED);
} catch (\Exception $e) {
$this->logger->error('Error creating post: ' . $e->getMessage());

View File

@@ -31,7 +31,6 @@ use OCP\AppFramework\Db\Entity;
* @method void setUpdatedAt(int $updatedAt)
*/
class ForumUser extends Entity implements JsonSerializable {
public $id;
protected string $userId = '';
protected int $postCount = 0;
protected int $threadCount = 0;

View File

@@ -17,33 +17,106 @@ class SeedHelper {
* Each function checks its own state and returns early if already seeded
*
* @param \OCP\Migration\IOutput|null $output Optional output for console messages
* @param bool $throwOnError If true, throws exceptions on failure. If false (default), logs errors and continues.
* Set to false when called from migrations to avoid PostgreSQL transaction abort issues.
*/
public static function seedAll($output = null): void {
public static function seedAll($output = null, bool $throwOnError = false): void {
$logger = \OC::$server->get(\Psr\Log\LoggerInterface::class);
$db = \OC::$server->get(\OCP\IDBConnection::class);
$logger->info('Forum seeding: Starting data seed/repair');
if ($output) {
$output->info('Forum: Starting data seed/repair...');
}
$errors = [];
// Ensure forum_users table exists (handle rename from forum_user_stats if needed)
self::ensureForumUsersTable($output);
// This is critical and should fail early if it cannot be done
try {
self::ensureForumUsersTable($output);
} catch (\Exception $e) {
$errors[] = 'ensureForumUsersTable: ' . $e->getMessage();
$logger->error('Forum seeding: Failed to ensure forum_users table', ['exception' => $e->getMessage()]);
if ($output) {
$output->warning(' Failed to ensure forum_users table: ' . $e->getMessage());
}
// Try to recover connection state for PostgreSQL
self::recoverConnectionState($db, $logger);
}
// Each function checks its own state and returns early if already seeded
// They run independently so one failure doesn't block others
self::seedDefaultRoles($output);
self::seedCategoryHeaders($output);
self::seedDefaultCategories($output);
self::seedCategoryPermissions($output);
self::seedGuestRolePermissions($output);
self::seedDefaultBBCodes($output);
self::assignUserRoles($output);
self::seedWelcomeThread($output);
// They run independently so one failure does not block others
// This is especially important for PostgreSQL where a failed query aborts the transaction
$seedOperations = [
'seedDefaultRoles' => fn () => self::seedDefaultRoles($output),
'seedCategoryHeaders' => fn () => self::seedCategoryHeaders($output),
'seedDefaultCategories' => fn () => self::seedDefaultCategories($output),
'seedCategoryPermissions' => fn () => self::seedCategoryPermissions($output),
'seedGuestRolePermissions' => fn () => self::seedGuestRolePermissions($output),
'seedDefaultBBCodes' => fn () => self::seedDefaultBBCodes($output),
'assignUserRoles' => fn () => self::assignUserRoles($output),
'seedWelcomeThread' => fn () => self::seedWelcomeThread($output),
];
$logger->info('Forum seeding: Completed data seed/repair');
foreach ($seedOperations as $name => $operation) {
try {
// Before each operation, ensure connection is in a clean state
self::recoverConnectionState($db, $logger);
$operation();
} catch (\Exception $e) {
$errors[] = "$name: " . $e->getMessage();
$logger->error("Forum seeding: $name failed", ['exception' => $e->getMessage()]);
// Try to recover connection state for next operation (especially important for PostgreSQL)
self::recoverConnectionState($db, $logger);
// Continue with other operations - don't let one failure block others
}
}
if ($output) {
$output->info('Forum: Data seed/repair completed');
if (!empty($errors)) {
$errorSummary = 'Some seeding operations failed: ' . implode('; ', $errors);
$logger->warning('Forum seeding: Completed with errors', ['errors' => $errors]);
if ($output) {
$output->warning('Forum: Data seed/repair completed with errors. Run "occ forum:repair-seeds" to retry failed operations.');
}
if ($throwOnError) {
throw new \RuntimeException($errorSummary);
}
} else {
$logger->info('Forum seeding: Completed data seed/repair successfully');
if ($output) {
$output->info('Forum: Data seed/repair completed');
}
}
}
/**
* Recover database connection state after an error
* On PostgreSQL, a failed query aborts the entire transaction, and subsequent queries fail.
* This method attempts to rollback any open transactions to restore a usable connection state.
*
* @param \OCP\IDBConnection $db Database connection
* @param \Psr\Log\LoggerInterface $logger Logger instance
*/
private static function recoverConnectionState(\OCP\IDBConnection $db, \Psr\Log\LoggerInterface $logger): void {
try {
// If we're in a transaction, try to roll back to recover the connection
while ($db->inTransaction()) {
try {
$db->rollBack();
$logger->debug('Forum seeding: Rolled back transaction to recover connection state');
} catch (\Exception $e) {
// If rollback fails, the connection might be in an unrecoverable state
$logger->warning('Forum seeding: Failed to rollback transaction during recovery', ['exception' => $e->getMessage()]);
break;
}
}
} catch (\Exception $e) {
// Ignore errors when checking transaction state
$logger->debug('Forum seeding: Error checking transaction state', ['exception' => $e->getMessage()]);
}
}
@@ -125,7 +198,8 @@ class SeedHelper {
/**
* Create the forum_users table from scratch
* This mirrors the schema from Version1 + Version8 migrations
* This mirrors the final schema from Version1 + Version2 + Version8 migrations
* (id as primary key, user_id as unique, includes signature column)
*/
private static function createForumUsersTable(\OCP\IDBConnection $db): void {
$platform = $db->getDatabasePlatform();
@@ -138,6 +212,7 @@ class SeedHelper {
// MySQL and MariaDB both extend MySQLPlatform
$db->executeStatement("
CREATE TABLE `{$tableName}` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` VARCHAR(64) NOT NULL,
`post_count` INT UNSIGNED NOT NULL DEFAULT 0,
`thread_count` INT UNSIGNED NOT NULL DEFAULT 0,
@@ -146,14 +221,17 @@ class SeedHelper {
`signature` TEXT DEFAULT NULL,
`created_at` INT UNSIGNED NOT NULL,
`updated_at` INT UNSIGNED NOT NULL,
PRIMARY KEY (`user_id`),
PRIMARY KEY (`id`),
UNIQUE INDEX `forum_users_user_id_uniq` (`user_id`),
INDEX `forum_users_post_count_idx` (`post_count`),
INDEX `forum_users_thread_count_idx` (`thread_count`),
INDEX `forum_users_deleted_at_idx` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
");
} elseif ($platform instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform) {
$db->executeStatement("
CREATE TABLE \"{$tableName}\" (
\"id\" BIGSERIAL PRIMARY KEY,
\"user_id\" VARCHAR(64) NOT NULL,
\"post_count\" INTEGER NOT NULL DEFAULT 0,
\"thread_count\" INTEGER NOT NULL DEFAULT 0,
@@ -161,16 +239,18 @@ class SeedHelper {
\"deleted_at\" INTEGER DEFAULT NULL,
\"signature\" TEXT DEFAULT NULL,
\"created_at\" INTEGER NOT NULL,
\"updated_at\" INTEGER NOT NULL,
PRIMARY KEY (\"user_id\")
\"updated_at\" INTEGER NOT NULL
)
");
$db->executeStatement("CREATE UNIQUE INDEX \"forum_users_user_id_uniq\" ON \"{$tableName}\" (\"user_id\")");
$db->executeStatement("CREATE INDEX \"forum_users_post_count_idx\" ON \"{$tableName}\" (\"post_count\")");
$db->executeStatement("CREATE INDEX \"forum_users_thread_count_idx\" ON \"{$tableName}\" (\"thread_count\")");
$db->executeStatement("CREATE INDEX \"forum_users_deleted_at_idx\" ON \"{$tableName}\" (\"deleted_at\")");
} else {
// SQLite (and any other platform as fallback)
$db->executeStatement("
CREATE TABLE \"{$tableName}\" (
\"id\" INTEGER PRIMARY KEY AUTOINCREMENT,
\"user_id\" VARCHAR(64) NOT NULL,
\"post_count\" INTEGER NOT NULL DEFAULT 0,
\"thread_count\" INTEGER NOT NULL DEFAULT 0,
@@ -178,11 +258,12 @@ class SeedHelper {
\"deleted_at\" INTEGER DEFAULT NULL,
\"signature\" TEXT DEFAULT NULL,
\"created_at\" INTEGER NOT NULL,
\"updated_at\" INTEGER NOT NULL,
PRIMARY KEY (\"user_id\")
\"updated_at\" INTEGER NOT NULL
)
");
$db->executeStatement("CREATE UNIQUE INDEX \"forum_users_user_id_uniq\" ON \"{$tableName}\" (\"user_id\")");
$db->executeStatement("CREATE INDEX \"forum_users_post_count_idx\" ON \"{$tableName}\" (\"post_count\")");
$db->executeStatement("CREATE INDEX \"forum_users_thread_count_idx\" ON \"{$tableName}\" (\"thread_count\")");
$db->executeStatement("CREATE INDEX \"forum_users_deleted_at_idx\" ON \"{$tableName}\" (\"deleted_at\")");
}
}
@@ -262,7 +343,8 @@ class SeedHelper {
$output->info(' → Creating default roles...');
}
$db->beginTransaction();
// Note: We don't use explicit transactions here to avoid PostgreSQL transaction abort cascade.
// Each INSERT is independent and idempotent, so partial success is acceptable.
$rolesCreated = 0;
// Define roles by role_type (not hardcoded IDs)
@@ -307,26 +389,29 @@ class SeedHelper {
foreach ($rolesToCreate as $roleType => $roleData) {
if (!in_array($roleType, $existingTypes)) {
$qb = $db->getQueryBuilder();
$qb->insert('forum_roles')
->values([
'name' => $qb->createNamedParameter($roleData['name']),
'description' => $qb->createNamedParameter($roleData['description']),
'can_access_admin_tools' => $qb->createNamedParameter($roleData['can_access_admin_tools'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_edit_roles' => $qb->createNamedParameter($roleData['can_edit_roles'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_edit_categories' => $qb->createNamedParameter($roleData['can_edit_categories'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'is_system_role' => $qb->createNamedParameter($roleData['is_system_role'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'role_type' => $qb->createNamedParameter($roleData['role_type'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_STR),
'created_at' => $qb->createNamedParameter($timestamp, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
])
->executeStatement();
$rolesCreated++;
$logger->info("Forum seeding: Created role with type '$roleType'");
try {
$qb = $db->getQueryBuilder();
$qb->insert('forum_roles')
->values([
'name' => $qb->createNamedParameter($roleData['name']),
'description' => $qb->createNamedParameter($roleData['description']),
'can_access_admin_tools' => $qb->createNamedParameter($roleData['can_access_admin_tools'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_edit_roles' => $qb->createNamedParameter($roleData['can_edit_roles'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_edit_categories' => $qb->createNamedParameter($roleData['can_edit_categories'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'is_system_role' => $qb->createNamedParameter($roleData['is_system_role'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'role_type' => $qb->createNamedParameter($roleData['role_type'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_STR),
'created_at' => $qb->createNamedParameter($timestamp, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
])
->executeStatement();
$rolesCreated++;
$logger->info("Forum seeding: Created role with type '$roleType'");
} catch (\Exception $e) {
// Log but continue - other roles might succeed
$logger->warning("Forum seeding: Failed to create role '$roleType': " . $e->getMessage());
}
}
}
$db->commit();
// Validate that critical roles can be found by role_type after creation
// Note: We query directly instead of using RoleMapper to avoid MultipleObjectsReturnedException
// if duplicates somehow exist (the cleanup migration should have removed them, but be defensive)
@@ -361,9 +446,6 @@ class SeedHelper {
$output->info(" ✓ Created $rolesCreated default roles (Admin, Moderator, User, Guest)");
}
} catch (\Exception $e) {
if ($db->inTransaction()) {
$db->rollBack();
}
$logger->error('Forum seeding: Failed to create default roles', [
'exception' => $e->getMessage(),
]);
@@ -456,7 +538,7 @@ class SeedHelper {
$userAccessibleCategories = $result->fetchAll();
$result->closeCursor();
$db->beginTransaction();
// Note: No explicit transaction - each INSERT auto-commits to avoid PostgreSQL transaction abort cascade
$categoriesGranted = 0;
foreach ($userAccessibleCategories as $categoryRow) {
$categoryId = (int)$categoryRow['category_id'];
@@ -472,30 +554,31 @@ class SeedHelper {
$result->closeCursor();
if (!$permExists) {
$qb = $db->getQueryBuilder();
$qb->insert('forum_category_perms')
->values([
'category_id' => $qb->createNamedParameter($categoryId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'role_id' => $qb->createNamedParameter($guestRoleId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'can_view' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_post' => $qb->createNamedParameter(false, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_reply' => $qb->createNamedParameter(false, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_moderate' => $qb->createNamedParameter(false, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
])
->executeStatement();
$categoriesGranted++;
try {
$qb = $db->getQueryBuilder();
$qb->insert('forum_category_perms')
->values([
'category_id' => $qb->createNamedParameter($categoryId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'role_id' => $qb->createNamedParameter($guestRoleId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'can_view' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_post' => $qb->createNamedParameter(false, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_reply' => $qb->createNamedParameter(false, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_moderate' => $qb->createNamedParameter(false, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
])
->executeStatement();
$categoriesGranted++;
} catch (\Exception $e) {
// Log but continue - other categories might succeed
$logger->warning("Forum seeding: Failed to set guest permission for category $categoryId: " . $e->getMessage());
}
}
}
$db->commit();
$logger->info('Forum seeding: Set guest role view-only permissions for ' . $categoriesGranted . ' categories (matching User role access)');
if ($output) {
$output->info(' ✓ Set guest role view-only permissions for ' . $categoriesGranted . ' categories');
}
} catch (\Exception $e) {
if ($db->inTransaction()) {
$db->rollBack();
}
$logger->error('Forum seeding: Failed to set guest role permissions', [
'exception' => $e->getMessage(),
]);
@@ -539,8 +622,7 @@ class SeedHelper {
$output->info(' → Creating category headers...');
}
$db->beginTransaction();
// Note: No explicit transaction - single INSERT auto-commits
// Create "General" category header
$qb = $db->getQueryBuilder();
$qb->insert('forum_cat_headers')
@@ -552,15 +634,11 @@ class SeedHelper {
])
->executeStatement();
$db->commit();
$logger->info('Forum seeding: Created category headers');
if ($output) {
$output->info(' ✓ Created category headers');
}
} catch (\Exception $e) {
if ($db->inTransaction()) {
$db->rollBack();
}
$logger->error('Forum seeding: Failed to create category headers', [
'exception' => $e->getMessage(),
]);
@@ -606,7 +684,7 @@ class SeedHelper {
}
$headerId = (int)$header['id'];
$db->beginTransaction();
// Note: No explicit transaction - each INSERT auto-commits to avoid PostgreSQL transaction abort cascade
$categoriesCreated = 0;
// Check if "General Discussions" category exists
@@ -619,22 +697,26 @@ class SeedHelper {
$result->closeCursor();
if (!$exists) {
// Create "General Discussions" category
$qb = $db->getQueryBuilder();
$qb->insert('forum_categories')
->values([
'header_id' => $qb->createNamedParameter($headerId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'name' => $qb->createNamedParameter($l->t('General discussions')),
'description' => $qb->createNamedParameter($l->t('A place for general conversations and discussions')),
'slug' => $qb->createNamedParameter('general-discussions'),
'sort_order' => $qb->createNamedParameter(0, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'thread_count' => $qb->createNamedParameter(0, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'post_count' => $qb->createNamedParameter(0, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'created_at' => $qb->createNamedParameter($timestamp, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'updated_at' => $qb->createNamedParameter($timestamp, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
])
->executeStatement();
$categoriesCreated++;
try {
// Create "General Discussions" category
$qb = $db->getQueryBuilder();
$qb->insert('forum_categories')
->values([
'header_id' => $qb->createNamedParameter($headerId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'name' => $qb->createNamedParameter($l->t('General discussions')),
'description' => $qb->createNamedParameter($l->t('A place for general conversations and discussions')),
'slug' => $qb->createNamedParameter('general-discussions'),
'sort_order' => $qb->createNamedParameter(0, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'thread_count' => $qb->createNamedParameter(0, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'post_count' => $qb->createNamedParameter(0, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'created_at' => $qb->createNamedParameter($timestamp, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'updated_at' => $qb->createNamedParameter($timestamp, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
])
->executeStatement();
$categoriesCreated++;
} catch (\Exception $e) {
$logger->warning('Forum seeding: Failed to create General Discussions category: ' . $e->getMessage());
}
}
// Check if "Support" category exists
@@ -647,33 +729,33 @@ class SeedHelper {
$result->closeCursor();
if (!$exists) {
// Create "Support" category
$qb = $db->getQueryBuilder();
$qb->insert('forum_categories')
->values([
'header_id' => $qb->createNamedParameter($headerId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'name' => $qb->createNamedParameter($l->t('Support')),
'description' => $qb->createNamedParameter($l->t('Ask questions about the forum, provide feedback or report issues.')),
'slug' => $qb->createNamedParameter('support'),
'sort_order' => $qb->createNamedParameter(1, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'thread_count' => $qb->createNamedParameter(0, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'post_count' => $qb->createNamedParameter(0, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'created_at' => $qb->createNamedParameter($timestamp, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'updated_at' => $qb->createNamedParameter($timestamp, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
])
->executeStatement();
$categoriesCreated++;
try {
// Create "Support" category
$qb = $db->getQueryBuilder();
$qb->insert('forum_categories')
->values([
'header_id' => $qb->createNamedParameter($headerId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'name' => $qb->createNamedParameter($l->t('Support')),
'description' => $qb->createNamedParameter($l->t('Ask questions about the forum, provide feedback or report issues.')),
'slug' => $qb->createNamedParameter('support'),
'sort_order' => $qb->createNamedParameter(1, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'thread_count' => $qb->createNamedParameter(0, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'post_count' => $qb->createNamedParameter(0, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'created_at' => $qb->createNamedParameter($timestamp, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'updated_at' => $qb->createNamedParameter($timestamp, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
])
->executeStatement();
$categoriesCreated++;
} catch (\Exception $e) {
$logger->warning('Forum seeding: Failed to create Support category: ' . $e->getMessage());
}
}
$db->commit();
$logger->info("Forum seeding: Created $categoriesCreated default categories");
if ($output) {
$output->info(" ✓ Created $categoriesCreated default categories (General Discussions, Support)");
}
} catch (\Exception $e) {
if ($db->inTransaction()) {
$db->rollBack();
}
$logger->error('Forum seeding: Failed to create default categories', [
'exception' => $e->getMessage(),
]);
@@ -743,7 +825,7 @@ class SeedHelper {
$output->info(' → Creating category permissions...');
}
$db->beginTransaction();
// Note: No explicit transaction - each INSERT auto-commits to avoid PostgreSQL transaction abort cascade
$permissionsCreated = 0;
// Create permissions for Moderator and User roles (Admin has implicit permissions)
@@ -761,18 +843,22 @@ class SeedHelper {
$result->closeCursor();
if (!$exists) {
$qb = $db->getQueryBuilder();
$qb->insert('forum_category_perms')
->values([
'category_id' => $qb->createNamedParameter($categoryId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'role_id' => $qb->createNamedParameter($moderatorRoleId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'can_view' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_post' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_reply' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_moderate' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
])
->executeStatement();
$permissionsCreated++;
try {
$qb = $db->getQueryBuilder();
$qb->insert('forum_category_perms')
->values([
'category_id' => $qb->createNamedParameter($categoryId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'role_id' => $qb->createNamedParameter($moderatorRoleId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'can_view' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_post' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_reply' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_moderate' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
])
->executeStatement();
$permissionsCreated++;
} catch (\Exception $e) {
$logger->warning("Forum seeding: Failed to create moderator permission for category $categoryId: " . $e->getMessage());
}
}
// Check and create User role permissions
@@ -786,30 +872,30 @@ class SeedHelper {
$result->closeCursor();
if (!$exists) {
$qb = $db->getQueryBuilder();
$qb->insert('forum_category_perms')
->values([
'category_id' => $qb->createNamedParameter($categoryId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'role_id' => $qb->createNamedParameter($userRoleId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'can_view' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_post' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_reply' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_moderate' => $qb->createNamedParameter(false, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
])
->executeStatement();
$permissionsCreated++;
try {
$qb = $db->getQueryBuilder();
$qb->insert('forum_category_perms')
->values([
'category_id' => $qb->createNamedParameter($categoryId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'role_id' => $qb->createNamedParameter($userRoleId, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
'can_view' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_post' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_reply' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'can_moderate' => $qb->createNamedParameter(false, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
])
->executeStatement();
$permissionsCreated++;
} catch (\Exception $e) {
$logger->warning("Forum seeding: Failed to create user permission for category $categoryId: " . $e->getMessage());
}
}
}
$db->commit();
$logger->info("Forum seeding: Created $permissionsCreated category permissions");
if ($output) {
$output->info(" ✓ Created $permissionsCreated category permissions for " . count($categories) . ' categories');
}
} catch (\Exception $e) {
if ($db->inTransaction()) {
$db->rollBack();
}
$logger->error('Forum seeding: Failed to create category permissions', [
'exception' => $e->getMessage(),
]);
@@ -836,8 +922,7 @@ class SeedHelper {
$output->info(' → Creating default BBCodes...');
}
$db->beginTransaction();
// Note: No explicit transaction - each INSERT auto-commits to avoid PostgreSQL transaction abort cascade
$bbcodes = [
[
'tag' => 'icode',
@@ -880,33 +965,33 @@ class SeedHelper {
$result->closeCursor();
if (!$exists) {
$qb = $db->getQueryBuilder();
$qb->insert('forum_bbcodes')
->values([
'tag' => $qb->createNamedParameter($bbcode['tag']),
'replacement' => $qb->createNamedParameter($bbcode['replacement']),
'example' => $qb->createNamedParameter($bbcode['example']),
'description' => $qb->createNamedParameter($bbcode['description']),
'enabled' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'parse_inner' => $qb->createNamedParameter($bbcode['parse_inner'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'is_builtin' => $qb->createNamedParameter($bbcode['is_builtin'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'special_handler' => $qb->createNamedParameter($bbcode['special_handler']),
'created_at' => $qb->createNamedParameter($timestamp, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
])
->executeStatement();
$bbcodesCreated++;
try {
$qb = $db->getQueryBuilder();
$qb->insert('forum_bbcodes')
->values([
'tag' => $qb->createNamedParameter($bbcode['tag']),
'replacement' => $qb->createNamedParameter($bbcode['replacement']),
'example' => $qb->createNamedParameter($bbcode['example']),
'description' => $qb->createNamedParameter($bbcode['description']),
'enabled' => $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'parse_inner' => $qb->createNamedParameter($bbcode['parse_inner'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'is_builtin' => $qb->createNamedParameter($bbcode['is_builtin'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
'special_handler' => $qb->createNamedParameter($bbcode['special_handler']),
'created_at' => $qb->createNamedParameter($timestamp, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
])
->executeStatement();
$bbcodesCreated++;
} catch (\Exception $e) {
$logger->warning("Forum seeding: Failed to create BBCode '{$bbcode['tag']}': " . $e->getMessage());
}
}
}
$db->commit();
$logger->info("Forum seeding: Created $bbcodesCreated default BBCodes");
if ($output) {
$output->info(" ✓ Created $bbcodesCreated default BBCodes (icode, spoiler, attachment)");
}
} catch (\Exception $e) {
if ($db->inTransaction()) {
$db->rollBack();
}
$logger->error('Forum seeding: Failed to create default BBCodes', [
'exception' => $e->getMessage(),
]);
@@ -1061,6 +1146,9 @@ class SeedHelper {
$logger = \OC::$server->get(\Psr\Log\LoggerInterface::class);
$timestamp = time();
// Recover connection state before starting (important for PostgreSQL)
self::recoverConnectionState($db, $logger);
try {
// Check if welcome thread already exists
$qb = $db->getQueryBuilder();
@@ -1112,6 +1200,42 @@ class SeedHelper {
}
});
// Check if slug column still exists BEFORE starting transaction
// (for backwards compatibility with old migrations)
// On PostgreSQL, a failed query inside a transaction aborts the entire transaction,
// so we must check column existence outside the transaction
$hasSlugColumn = true;
try {
$checkQb = $db->getQueryBuilder();
$checkQb->select('slug')->from('forum_posts')->setMaxResults(1);
$checkQb->executeQuery()->closeCursor();
} catch (\Exception $e) {
$hasSlugColumn = false;
// Recover connection state after the failed query (important for PostgreSQL)
self::recoverConnectionState($db, $logger);
}
// Prepare welcome post content
$welcomeContent = $l->t('Welcome to the Nextcloud Forums!') . "\n\n"
. $l->t('This is a community-driven forum built right into your Nextcloud instance. '
. 'Here you can discuss topics, share ideas and collaborate with other users.') . "\n\n"
. '[b]' . $l->t('Features:') . "[/b]\n"
. "[list]\n"
. '[*]' . $l->t('Create and reply to threads') . "\n"
. '[*]' . $l->t('Organize discussions by categories') . "\n"
. '[*]' . $l->t('Use BBCode for rich text formatting') . "\n"
. '[*]' . $l->t('Attach files from your Nextcloud storage') . "\n"
. '[*]' . $l->t('React to posts') . "\n"
. '[*]' . $l->t('Track read/unread threads') . "\n\n"
. "[/list]\n"
. '[b]' . $l->t('BBCode examples:') . "[/b]\n"
. "[list]\n"
. '[*][b]' . $l->t('Bold text') . '[/b] - ' . $l->t('Use %1$stext%2$s', ['[icode][b]', '[/b][/icode]']) . "\n"
. '[*][i]' . $l->t('Italic text') . '[/i] - ' . $l->t('Use %1$stext%2$s', ['[icode][i]', '[/i][/icode]']) . "\n"
. '[*][u]' . $l->t('Underlined text') . '[/u] - ' . $l->t('Use %1$stext%2$s', ['[icode][u]', '[/u][/icode]']) . "\n\n"
. "[/list]\n"
. $l->t('Feel free to start a new discussion or reply to existing threads. Happy posting!');
$db->beginTransaction();
// Create welcome thread
@@ -1134,38 +1258,6 @@ class SeedHelper {
->executeStatement();
$threadId = $qb->getLastInsertId();
// Create welcome post
$welcomeContent = $l->t('Welcome to the Nextcloud Forums!') . "\n\n"
. $l->t('This is a community-driven forum built right into your Nextcloud instance. '
. 'Here you can discuss topics, share ideas and collaborate with other users.') . "\n\n"
. '[b]' . $l->t('Features:') . "[/b]\n"
. "[list]\n"
. '[*]' . $l->t('Create and reply to threads') . "\n"
. '[*]' . $l->t('Organize discussions by categories') . "\n"
. '[*]' . $l->t('Use BBCode for rich text formatting') . "\n"
. '[*]' . $l->t('Attach files from your Nextcloud storage') . "\n"
. '[*]' . $l->t('React to posts') . "\n"
. '[*]' . $l->t('Track read/unread threads') . "\n\n"
. "[/list]\n"
. '[b]' . $l->t('BBCode examples:') . "[/b]\n"
. "[list]\n"
. '[*][b]' . $l->t('Bold text') . '[/b] - ' . $l->t('Use %1$stext%2$s', ['[icode][b]', '[/b][/icode]']) . "\n"
. '[*][i]' . $l->t('Italic text') . '[/i] - ' . $l->t('Use %1$stext%2$s', ['[icode][i]', '[/i][/icode]']) . "\n"
. '[*][u]' . $l->t('Underlined text') . '[/u] - ' . $l->t('Use %1$stext%2$s', ['[icode][u]', '[/u][/icode]']) . "\n\n"
. "[/list]\n"
. $l->t('Feel free to start a new discussion or reply to existing threads. Happy posting!');
// Check if slug column still exists (for backwards compatibility with old migrations)
// Use a query to check column existence since schema introspection APIs vary
$hasSlugColumn = true;
try {
$checkQb = $db->getQueryBuilder();
$checkQb->select('slug')->from('forum_posts')->setMaxResults(1);
$checkQb->executeQuery()->closeCursor();
} catch (\Exception $e) {
$hasSlugColumn = false;
}
// Build post values - slug is optional (removed in Version8)
$qb = $db->getQueryBuilder();
$postValues = [
@@ -1265,8 +1357,13 @@ class SeedHelper {
$output->info(' ✓ Created welcome thread');
}
} catch (\Exception $e) {
if ($db->inTransaction()) {
$db->rollBack();
// Try to rollback if we're in a transaction - important for PostgreSQL recovery
try {
if ($db->inTransaction()) {
$db->rollBack();
}
} catch (\Exception $rollbackEx) {
$logger->debug('Forum seeding: Failed to rollback after welcome thread error', ['exception' => $rollbackEx->getMessage()]);
}
$logger->error('Forum seeding: Failed to create welcome thread', [
'exception' => $e->getMessage(),

View File

@@ -75,8 +75,7 @@ class Version15Date20260103000000 extends SimpleMigrationStep {
* @param array $options
*/
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
// Re-run seeding to ensure all required data exists
SeedHelper::seedAll($output);
// No-op: Seeding moved to Version16 which removes the incorrect unique constraint first
}
/**

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\Forum\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
use Psr\Log\LoggerInterface;
/**
* Version 16 Migration:
* - Remove unique constraint on role_type column to allow multiple custom roles
* - Re-run seeding to ensure all required data exists
*
* The unique constraint was incorrectly added in Version15, which prevented
* creating more than one custom role (since all custom roles have role_type='custom').
*/
class Version16Date20260117000000 extends SimpleMigrationStep {
public function __construct(
private LoggerInterface $logger,
) {
}
/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
* @return ISchemaWrapper|null
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if ($schema->hasTable('forum_roles')) {
$table = $schema->getTable('forum_roles');
// Remove the unique index on role_type if it exists
// This was incorrectly added in Version15 and prevents creating multiple custom roles
if ($table->hasIndex('forum_roles_role_type_uniq')) {
$output->info('Forum: Removing unique constraint on role_type to allow multiple custom roles...');
$table->dropIndex('forum_roles_role_type_uniq');
return $schema;
}
}
return null;
}
/**
* @param IOutput $output
* @param Closure(): ISchemaWrapper $schemaClosure
* @param array $options
*/
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
// Re-run seeding to ensure all required data exists
// Pass throwOnError=false to avoid PostgreSQL transaction abort issues
// If seeding fails, users can run "occ forum:repair-seeds" to retry
try {
SeedHelper::seedAll($output, false);
} catch (\Exception $e) {
// This should not happen with throwOnError=false, but handle it gracefully
$this->logger->error('Forum migration: Seeding failed unexpectedly', ['exception' => $e->getMessage()]);
$output->warning('Forum: Seeding failed. Run "occ forum:repair-seeds" after enabling the app to complete setup.');
}
}
}

View File

@@ -85,12 +85,30 @@ class Version1Date20251106004226 extends SimpleMigrationStep {
$table->addIndex(['name'], 'forum_roles_name_idx');
}
/**
* Create forum_users table (formerly forum_user_stats)
* Note: On fresh installs, this creates forum_users directly with the final schema.
* For progressive installs where forum_user_stats already exists,
* SeedHelper::ensureForumUsersTable() handles the rename.
*
* The table structure matches what Version2 transforms it to:
* - id: auto-increment primary key
* - user_id: unique string
* - signature: added in Version8
*/
private function createUserStatsTable(ISchemaWrapper $schema): void {
if ($schema->hasTable('forum_user_stats')) {
// Skip if either table already exists (handles both fresh and progressive installs)
if ($schema->hasTable('forum_users') || $schema->hasTable('forum_user_stats')) {
return;
}
$table = $schema->createTable('forum_user_stats');
// Create forum_users directly with the final schema (matching Version2's transformation)
$table = $schema->createTable('forum_users');
$table->addColumn('id', 'bigint', [
'autoincrement' => true,
'notnull' => true,
'unsigned' => true,
]);
$table->addColumn('user_id', 'string', [
'notnull' => true,
'length' => 64,
@@ -115,6 +133,10 @@ class Version1Date20251106004226 extends SimpleMigrationStep {
'unsigned' => true,
'default' => null,
]);
$table->addColumn('signature', 'text', [
'notnull' => false,
'default' => null,
]);
$table->addColumn('created_at', 'integer', [
'notnull' => true,
'unsigned' => true,
@@ -123,9 +145,11 @@ class Version1Date20251106004226 extends SimpleMigrationStep {
'notnull' => true,
'unsigned' => true,
]);
$table->setPrimaryKey(['user_id']);
$table->addIndex(['post_count'], 'user_stats_post_count_idx');
$table->addIndex(['deleted_at'], 'user_stats_deleted_at_idx');
$table->setPrimaryKey(['id']);
$table->addUniqueIndex(['user_id'], 'forum_users_user_id_uniq');
$table->addIndex(['post_count'], 'forum_users_post_count_idx');
$table->addIndex(['thread_count'], 'forum_users_thread_count_idx');
$table->addIndex(['deleted_at'], 'forum_users_deleted_at_idx');
}
private function createForumUserRolesTable(ISchemaWrapper $schema): void {

View File

@@ -67,18 +67,38 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
$table->addUniqueIndex(['user_id', 'thread_id'], 'thread_subs_uniq_idx');
}
/**
* Fix forum_user_stats or forum_users table structure
* Handles both old table name (progressive installs) and new table name (fresh installs)
*/
private function fixForumUserStatsTable(ISchemaWrapper $schema): void {
if (!$schema->hasTable('forum_user_stats')) {
// Determine which table exists (handles both fresh and progressive installs)
$tableName = null;
if ($schema->hasTable('forum_user_stats')) {
$tableName = 'forum_user_stats';
} elseif ($schema->hasTable('forum_users')) {
$tableName = 'forum_users';
}
if ($tableName === null) {
return;
}
$table = $schema->getTable('forum_user_stats');
$table = $schema->getTable($tableName);
// Check if already fixed (has id column)
// Note: On fresh installs, forum_users uses user_id as primary key (no id column needed)
// This fix is only needed for progressive installs with old forum_user_stats structure
if ($table->hasColumn('id')) {
return;
}
// Only add id column to forum_user_stats (old structure)
// forum_users created in Version1 uses user_id as primary key and doesn't need this fix
if ($tableName !== 'forum_user_stats') {
return;
}
// Add id column as auto-increment
$table->addColumn('id', 'bigint', [
'autoincrement' => true,
@@ -123,8 +143,7 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
}
/**
* Rebuild user stats using the old table name (forum_user_stats)
* This is needed because Version8 hasn't renamed the table yet
* Rebuild user stats - handles both old (forum_user_stats) and new (forum_users) table names
*/
private function rebuildAllUserStatsLegacy(): array {
// Get all user IDs from Nextcloud
@@ -133,11 +152,22 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
$users[] = $user->getUID();
});
// Determine which table to use
$tableName = $this->getUserStatsTableName();
if ($tableName === null) {
// No table exists yet - this shouldn't happen but handle gracefully
return [
'users' => count($users),
'updated' => 0,
'created' => 0,
];
}
$updated = 0;
$created = 0;
foreach ($users as $userId) {
$wasCreated = $this->rebuildUserStatsLegacy($userId);
$wasCreated = $this->rebuildUserStatsLegacy($userId, $tableName);
if ($wasCreated) {
$created++;
} else {
@@ -153,9 +183,36 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
}
/**
* Rebuild stats for a single user using the old table name
* Get the user stats table name (handles both old and new names)
*/
private function rebuildUserStatsLegacy(string $userId): bool {
private function getUserStatsTableName(): ?string {
// Check forum_users first (new name, for fresh installs)
// Then check forum_user_stats (old name, for progressive installs)
try {
$qb = $this->db->getQueryBuilder();
$qb->select('user_id')->from('forum_users')->setMaxResults(1);
$qb->executeQuery()->closeCursor();
return 'forum_users';
} catch (\Exception $e) {
// Table doesn't exist, try old name
}
try {
$qb = $this->db->getQueryBuilder();
$qb->select('user_id')->from('forum_user_stats')->setMaxResults(1);
$qb->executeQuery()->closeCursor();
return 'forum_user_stats';
} catch (\Exception $e) {
// Neither table exists
}
return null;
}
/**
* Rebuild stats for a single user
*/
private function rebuildUserStatsLegacy(string $userId, string $tableName): bool {
// Count non-deleted threads created by this user
$threadQb = $this->db->getQueryBuilder();
$threadQb->select($threadQb->func()->count('*', 'count'))
@@ -194,10 +251,10 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
$lastPostAt = $lastPostResult->fetchOne();
$lastPostResult->closeCursor();
// Check if forum user record already exists (using OLD table name)
// Check if forum user record already exists
$checkQb = $this->db->getQueryBuilder();
$checkQb->select('user_id')
->from('forum_user_stats') // OLD table name!
->from($tableName)
->where($checkQb->expr()->eq('user_id', $checkQb->createNamedParameter($userId)));
$checkResult = $checkQb->executeQuery();
$exists = $checkResult->fetch();
@@ -206,9 +263,9 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
$timestamp = time();
if ($exists) {
// Update existing record (using OLD table name)
// Update existing record
$updateQb = $this->db->getQueryBuilder();
$updateQb->update('forum_user_stats') // OLD table name!
$updateQb->update($tableName)
->set('thread_count', $updateQb->createNamedParameter($threadCount, IQueryBuilder::PARAM_INT))
->set('post_count', $updateQb->createNamedParameter($postCount, IQueryBuilder::PARAM_INT))
->set('updated_at', $updateQb->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))
@@ -221,9 +278,9 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
$updateQb->executeStatement();
return false;
} else {
// Create new record (using OLD table name)
// Create new record
$insertQb = $this->db->getQueryBuilder();
$insertQb->insert('forum_user_stats') // OLD table name!
$insertQb->insert($tableName)
->values([
'user_id' => $insertQb->createNamedParameter($userId),
'thread_count' => $insertQb->createNamedParameter($threadCount, IQueryBuilder::PARAM_INT),
@@ -239,7 +296,7 @@ class Version2Date20251114222614 extends SimpleMigrationStep {
} catch (\Exception $e) {
// If insert fails (race condition), try updating instead
$updateQb = $this->db->getQueryBuilder();
$updateQb->update('forum_user_stats') // OLD table name!
$updateQb->update($tableName)
->set('thread_count', $updateQb->createNamedParameter($threadCount, IQueryBuilder::PARAM_INT))
->set('post_count', $updateQb->createNamedParameter($postCount, IQueryBuilder::PARAM_INT))
->set('updated_at', $updateQb->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))

View File

@@ -43,9 +43,18 @@ class Version8Date20251128000000 extends SimpleMigrationStep {
}
}
// Add signature column to user stats
if ($schema->hasTable('forum_user_stats')) {
$table = $schema->getTable('forum_user_stats');
// Add signature column to forum_users table (handles both old and new table names)
// On fresh installs: forum_users is created with signature column in Version1
// On progressive installs: forum_user_stats may still exist and needs signature added
$userTableName = null;
if ($schema->hasTable('forum_users')) {
$userTableName = 'forum_users';
} elseif ($schema->hasTable('forum_user_stats')) {
$userTableName = 'forum_user_stats';
}
if ($userTableName !== null) {
$table = $schema->getTable($userTableName);
if (!$table->hasColumn('signature')) {
$table->addColumn('signature', 'text', [

View File

@@ -17,6 +17,9 @@ class UserPreferencesService {
/** Preference key for auto-subscribing to created threads */
public const PREF_AUTO_SUBSCRIBE_CREATED_THREADS = 'auto_subscribe_created_threads';
/** Preference key for auto-subscribing to threads when replying */
public const PREF_AUTO_SUBSCRIBE_REPLIED_THREADS = 'auto_subscribe_replied_threads';
/** Preference key for upload directory path */
public const PREF_UPLOAD_DIRECTORY = 'upload_directory';
@@ -26,6 +29,7 @@ class UserPreferencesService {
/** @var array<string, mixed> Default preference values */
private const DEFAULTS = [
self::PREF_AUTO_SUBSCRIBE_CREATED_THREADS => true,
self::PREF_AUTO_SUBSCRIBE_REPLIED_THREADS => false,
self::PREF_UPLOAD_DIRECTORY => 'Forum',
self::PREF_SIGNATURE => '',
];
@@ -33,6 +37,7 @@ class UserPreferencesService {
/** @var array<string> List of valid preference keys */
private const VALID_KEYS = [
self::PREF_AUTO_SUBSCRIBE_CREATED_THREADS,
self::PREF_AUTO_SUBSCRIBE_REPLIED_THREADS,
self::PREF_UPLOAD_DIRECTORY,
self::PREF_SIGNATURE,
];

View File

@@ -2729,6 +2729,13 @@
"schema": {
"type": "object",
"properties": {
"headerId": {
"type": "integer",
"format": "int64",
"nullable": true,
"default": null,
"description": "Category header ID"
},
"name": {
"type": "string",
"nullable": true,

View File

@@ -28,7 +28,7 @@
"@nextcloud/l10n": "^3.4.1",
"@nextcloud/router": "^3.1.0",
"@nextcloud/vite-config": "2.3.5",
"@nextcloud/vue": "^9.3.1",
"@nextcloud/vue": "^9.3.3",
"date-fns": "^4.1.0",
"linkifyjs": "^4.3.2",
"vue": "^3.5.26",
@@ -43,16 +43,16 @@
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.8.1",
"eslint": "^9.39.2",
"happy-dom": "^20.0.11",
"happy-dom": "^20.1.0",
"husky": "^9.1.7",
"lint-staged": "^16.2.7",
"prettier": "^2.8.8",
"prettier-plugin-vue": "^1.1.6",
"rollup-plugin-visualizer": "^6.0.5",
"sass": "^1.97.1",
"sass-embedded": "^1.97.1",
"sass": "^1.97.2",
"sass-embedded": "^1.97.2",
"typescript": "5.9.2",
"typescript-eslint": "^8.51.0",
"typescript-eslint": "^8.52.0",
"vite": "^6.4.1",
"vite-plugin-checker": "^0.12.0",
"vitest": "^4.0.16",

806
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -445,6 +445,12 @@ export default defineComponent({
line-height: 1.6;
}
}
// Images ([img]) - auto-scale to fit content width
:deep(img) {
max-width: 100%;
height: auto;
}
}
.icon {

View File

@@ -304,6 +304,12 @@ export default defineComponent({
line-height: 1.6;
}
}
// Images - auto-scale to fit content width
:deep(img) {
max-width: 100%;
height: auto;
}
}
}
</style>

View File

@@ -54,12 +54,12 @@ describe('bbcode utilities', () => {
expect(result.cursorPosition).toBe(18)
})
it('inserts template at cursor when no selection', () => {
it('inserts template at cursor when no selection, cursor between tags', () => {
const selection: TextSelection = { text: 'Hello world', start: 6, end: 6 }
const result = applyBBCodeTemplate(selection, { template: '[b]{text}[/b]' })
expect(result.text).toBe('Hello [b][/b]world')
expect(result.cursorPosition).toBe(13) // cursor after [/b]
expect(result.cursorPosition).toBe(9) // cursor between tags (after [b]) for immediate typing
})
it('uses fallback text when no selection', () => {
@@ -130,6 +130,83 @@ describe('bbcode utilities', () => {
expect(result.text).toBe('[size=]text[/size]')
})
describe('cursor positioning', () => {
it('places cursor between tags when no selection (simple tag)', () => {
const selection: TextSelection = { text: '', start: 0, end: 0 }
const result = applyBBCodeTemplate(selection, { template: '[b]{text}[/b]' })
expect(result.text).toBe('[b][/b]')
expect(result.cursorPosition).toBe(3) // right after [b]
})
it('places cursor between tags when no selection (tag with value)', () => {
const selection: TextSelection = { text: '', start: 0, end: 0 }
const result = applyBBCodeTemplate(selection, {
template: '[url={value}]{text}[/url]',
value: 'http://example.com',
})
expect(result.text).toBe('[url=http://example.com][/url]')
expect(result.cursorPosition).toBe(24) // right after the ]
})
it('places cursor between tags when no selection (color tag)', () => {
const selection: TextSelection = { text: '', start: 0, end: 0 }
const result = applyBBCodeTemplate(selection, {
template: '[color={value}]{text}[/color]',
value: '#ff0000',
})
expect(result.text).toBe('[color=#ff0000][/color]')
expect(result.cursorPosition).toBe(15) // right after [color=#ff0000]
})
it('places cursor after closing tag when selection exists', () => {
const selection: TextSelection = { text: 'hello', start: 0, end: 5 }
const result = applyBBCodeTemplate(selection, { template: '[b]{text}[/b]' })
expect(result.text).toBe('[b]hello[/b]')
expect(result.cursorPosition).toBe(12) // after [/b]
})
it('places cursor after closing tag when fallback text is used', () => {
const selection: TextSelection = { text: '', start: 0, end: 0 }
const result = applyBBCodeTemplate(selection, {
template: '[b]{text}[/b]',
fallbackText: 'bold',
})
expect(result.text).toBe('[b]bold[/b]')
expect(result.cursorPosition).toBe(11) // after [/b]
})
it('places cursor between tags for quote tag without selection', () => {
const selection: TextSelection = { text: 'Some text', start: 9, end: 9 }
const result = applyBBCodeTemplate(selection, { template: '[quote]{text}[/quote]' })
expect(result.text).toBe('Some text[quote][/quote]')
expect(result.cursorPosition).toBe(16) // right after [quote]
})
it('places cursor between tags for code tag without selection', () => {
const selection: TextSelection = { text: '', start: 0, end: 0 }
const result = applyBBCodeTemplate(selection, { template: '[code]{text}[/code]' })
expect(result.text).toBe('[code][/code]')
expect(result.cursorPosition).toBe(6) // right after [code]
})
it('handles template with newlines - cursor between tags', () => {
const selection: TextSelection = { text: '', start: 0, end: 0 }
const result = applyBBCodeTemplate(selection, {
template: '[list]\n[*]{text}\n[/list]',
})
expect(result.text).toBe('[list]\n[*]\n[/list]')
expect(result.cursorPosition).toBe(10) // right after [*]
})
})
})
describe('insertTextAtSelection', () => {
@@ -183,12 +260,12 @@ describe('bbcode utilities', () => {
expect(result.cursorPosition).toBe(18)
})
it('inserts empty tags when no selection', () => {
it('inserts empty tags when no selection, cursor between tags', () => {
const selection: TextSelection = { text: 'Hello', start: 5, end: 5 }
const result = wrapSelection(selection, '[i]', '[/i]')
expect(result.text).toBe('Hello[i][/i]')
expect(result.cursorPosition).toBe(12)
expect(result.cursorPosition).toBe(8) // cursor between tags (after [i]) for immediate typing
})
it('uses fallback text when no selection', () => {
@@ -419,12 +496,12 @@ describe('bbcode utilities', () => {
expect(result.text).toBe('Use [code][b][/code] for bold')
})
it('handles empty string', () => {
it('handles empty string, cursor between tags', () => {
const selection: TextSelection = { text: '', start: 0, end: 0 }
const result = wrapSelection(selection, '[b]', '[/b]')
expect(result.text).toBe('[b][/b]')
expect(result.cursorPosition).toBe(7)
expect(result.cursorPosition).toBe(3) // cursor between tags (after [b]) for immediate typing
})
it('handles very long text', () => {

View File

@@ -74,6 +74,11 @@ export function getSelectedText(selection: TextSelection): string {
* 2. Replaces the selected text with the BBCode-wrapped version
* 3. Returns the new text and cursor position
*
* Cursor positioning:
* - With selected text or fallback text: cursor is placed after the closing tag
* - Without any content: cursor is placed between the opening and closing tags
* so the user can immediately start typing
*
* Template placeholders:
* - {text}: Replaced with selected text (or fallbackText if nothing selected)
* - {value}: Replaced with the provided value (for tags like [url=...], [color=...])
@@ -83,7 +88,7 @@ export function getSelectedText(selection: TextSelection): string {
* @returns The insertion result with new text and cursor position
*
* @example
* // Simple wrap with [b] tags
* // Simple wrap with [b] tags - cursor after closing tag
* applyBBCodeTemplate(
* { text: 'Hello world', start: 6, end: 11 },
* { template: '[b]{text}[/b]' }
@@ -91,6 +96,14 @@ export function getSelectedText(selection: TextSelection): string {
* // Returns: { text: 'Hello [b]world[/b]', cursorPosition: 18 }
*
* @example
* // No selection - cursor between tags for immediate typing
* applyBBCodeTemplate(
* { text: 'Hello ', start: 6, end: 6 },
* { template: '[b]{text}[/b]' }
* )
* // Returns: { text: 'Hello [b][/b]', cursorPosition: 9 }
*
* @example
* // URL with value
* applyBBCodeTemplate(
* { text: 'Check this', start: 6, end: 10 },
@@ -115,7 +128,27 @@ export function applyBBCodeTemplate(
.replace('{text}', contentText)
const newText = beforeText + insertText + afterText
const cursorPosition = beforeText.length + insertText.length
// Calculate cursor position:
// - If there's content (selected text or fallback), place cursor after the closing tag
// - If no content, place cursor between tags (at the {text} placeholder position)
// so user can immediately start typing
let cursorPosition: number
if (contentText) {
// Cursor after the entire inserted text (after closing tag)
cursorPosition = beforeText.length + insertText.length
} else {
// No content - find where {text} was in the template and place cursor there
const templateWithValue = template.template.replace('{value}', template.value || '')
const textPlaceholderIndex = templateWithValue.indexOf('{text}')
if (textPlaceholderIndex !== -1) {
// Place cursor where {text} placeholder was (between tags)
cursorPosition = beforeText.length + textPlaceholderIndex
} else {
// Fallback: place cursor at end of inserted text
cursorPosition = beforeText.length + insertText.length
}
}
return {
text: newText,

View File

@@ -43,9 +43,16 @@
<div class="preference-item">
<NcCheckboxRadioSwitch v-model="formData.auto_subscribe_created_threads">
{{ strings.autoSubscribeLabel }}
{{ strings.autoSubscribeCreatedLabel }}
</NcCheckboxRadioSwitch>
<p class="preference-hint">{{ strings.autoSubscribeHint }}</p>
<p class="preference-hint">{{ strings.autoSubscribeCreatedHint }}</p>
</div>
<div class="preference-item">
<NcCheckboxRadioSwitch v-model="formData.auto_subscribe_replied_threads">
{{ strings.autoSubscribeRepliedLabel }}
</NcCheckboxRadioSwitch>
<p class="preference-hint">{{ strings.autoSubscribeRepliedHint }}</p>
</div>
</div>
@@ -134,6 +141,7 @@ import { getFilePickerBuilder, FilePickerType } from '@nextcloud/dialogs'
interface UserPreferences {
auto_subscribe_created_threads: boolean
auto_subscribe_replied_threads: boolean
upload_directory: string
signature: string
}
@@ -162,11 +170,13 @@ export default defineComponent({
error: null as string | null,
originalData: {
auto_subscribe_created_threads: true,
auto_subscribe_replied_threads: false,
upload_directory: 'Forum',
signature: '',
} as UserPreferences,
formData: {
auto_subscribe_created_threads: true,
auto_subscribe_replied_threads: false,
upload_directory: 'Forum',
signature: '',
} as UserPreferences,
@@ -180,11 +190,16 @@ export default defineComponent({
retry: t('forum', 'Retry'),
subscriptionsTitle: t('forum', 'Notifications'),
subscriptionsDesc: t('forum', 'Configure how you receive notifications'),
autoSubscribeLabel: t('forum', 'Auto-subscribe to threads I create'),
autoSubscribeHint: t(
autoSubscribeCreatedLabel: t('forum', 'Auto-subscribe to threads I create'),
autoSubscribeCreatedHint: t(
'forum',
'When enabled, you will automatically receive notifications for replies to threads you create',
),
autoSubscribeRepliedLabel: t('forum', 'Auto-subscribe to threads I reply to'),
autoSubscribeRepliedHint: t(
'forum',
'When enabled, you will automatically receive notifications for new replies in threads you have replied to',
),
filesTitle: t('forum', 'Files'),
filesDesc: t('forum', 'Configure file upload settings'),
uploadDirectoryLabel: t('forum', 'Upload directory'),
@@ -212,6 +227,8 @@ export default defineComponent({
return (
this.formData.auto_subscribe_created_threads !==
this.originalData.auto_subscribe_created_threads ||
this.formData.auto_subscribe_replied_threads !==
this.originalData.auto_subscribe_replied_threads ||
this.formData.upload_directory !== this.originalData.upload_directory ||
this.formData.signature !== this.originalData.signature
)

View File

@@ -0,0 +1,406 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\Forum\Tests\Integration\Migration;
use OCA\Forum\Db\Role;
use OCA\Forum\Migration\SeedHelper;
use OCP\IDBConnection;
use PHPUnit\Framework\TestCase;
/**
* Integration tests for SeedHelper
*
* These tests run against a real database to ensure seeding works correctly
* on both MySQL and PostgreSQL. They verify:
* - Seeding creates required data on a clean database
* - Seeding is idempotent (running twice doesn't cause errors)
* - Individual seed operations can fail without breaking others
* - Transaction handling works correctly on PostgreSQL
*
* Note: These tests require the Forum app tables to exist. They will be skipped
* if run in an environment where migrations haven't been run (e.g., local dev with SQLite).
* In CI, the app is properly installed with tables before tests run.
*
* @group integration
* @group database
*/
class SeedHelperTest extends TestCase {
private IDBConnection $db;
private bool $tablesExist = false;
protected function setUp(): void {
parent::setUp();
$this->db = \OC::$server->get(IDBConnection::class);
// Check if forum tables exist (they might not in local dev environment)
$this->tablesExist = $this->checkTablesExist();
if (!$this->tablesExist) {
$this->markTestSkipped('Forum tables do not exist. Run these tests in CI or with a fully installed Nextcloud instance.');
}
}
/**
* Check if the forum tables exist in the database
*/
private function checkTablesExist(): bool {
try {
$qb = $this->db->getQueryBuilder();
$qb->select('id')
->from('forum_roles')
->setMaxResults(1);
$qb->executeQuery()->closeCursor();
return true;
} catch (\Exception $e) {
return false;
}
}
protected function tearDown(): void {
// Clean up test data after each test
$this->cleanupTestData();
parent::tearDown();
}
/**
* Clean up all forum data to start fresh
*/
private function cleanupTestData(): void {
$tables = [
'forum_thread_subs',
'forum_read_markers',
'forum_reactions',
'forum_posts',
'forum_threads',
'forum_category_perms',
'forum_categories',
'forum_cat_headers',
'forum_user_roles',
'forum_users',
'forum_bbcodes',
'forum_roles',
];
foreach ($tables as $table) {
try {
$qb = $this->db->getQueryBuilder();
$qb->delete($table)->executeStatement();
} catch (\Exception $e) {
// Table might not exist, ignore
}
}
}
/**
* Test that seedAll creates all required data on a clean database
*/
public function testSeedAllCreatesRequiredData(): void {
// Clean state
$this->cleanupTestData();
// Run seeding
SeedHelper::seedAll(null, false);
// Verify roles were created
$this->assertRolesExist();
// Verify category headers were created
$this->assertCategoryHeadersExist();
// Verify categories were created
$this->assertCategoriesExist();
// Verify BBCodes were created
$this->assertBBCodesExist();
// Verify category permissions were created
$this->assertCategoryPermissionsExist();
}
/**
* Test that seedAll is idempotent - running twice doesn't cause errors
*/
public function testSeedAllIsIdempotent(): void {
// Clean state
$this->cleanupTestData();
// Run seeding twice
SeedHelper::seedAll(null, false);
SeedHelper::seedAll(null, false);
// Verify data exists (and isn't duplicated)
$this->assertRolesExist();
$this->assertNoDuplicateRoles();
}
/**
* Test that seedDefaultRoles creates all four roles
*/
public function testSeedDefaultRolesCreatesAllRoles(): void {
$this->cleanupTestData();
SeedHelper::seedDefaultRoles(null);
$qb = $this->db->getQueryBuilder();
$qb->select('role_type')
->from('forum_roles');
$result = $qb->executeQuery();
$roles = $result->fetchAll();
$result->closeCursor();
$roleTypes = array_column($roles, 'role_type');
$this->assertContains(Role::ROLE_TYPE_ADMIN, $roleTypes);
$this->assertContains(Role::ROLE_TYPE_MODERATOR, $roleTypes);
$this->assertContains(Role::ROLE_TYPE_DEFAULT, $roleTypes);
$this->assertContains(Role::ROLE_TYPE_GUEST, $roleTypes);
$this->assertCount(4, $roles, 'Should have exactly 4 roles');
}
/**
* Test that seedDefaultRoles is idempotent
*/
public function testSeedDefaultRolesIsIdempotent(): void {
$this->cleanupTestData();
SeedHelper::seedDefaultRoles(null);
SeedHelper::seedDefaultRoles(null);
$qb = $this->db->getQueryBuilder();
$qb->select($qb->func()->count('*', 'count'))
->from('forum_roles');
$result = $qb->executeQuery();
$row = $result->fetch();
$result->closeCursor();
$this->assertEquals(4, (int)$row['count'], 'Should still have exactly 4 roles after running twice');
}
/**
* Test that seedCategoryHeaders creates the General header
*/
public function testSeedCategoryHeadersCreatesHeader(): void {
$this->cleanupTestData();
SeedHelper::seedCategoryHeaders(null);
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('forum_cat_headers');
$result = $qb->executeQuery();
$headers = $result->fetchAll();
$result->closeCursor();
$this->assertCount(1, $headers);
$this->assertNotEmpty($headers[0]['name']);
}
/**
* Test that seedDefaultCategories creates categories
*/
public function testSeedDefaultCategoriesCreatesCategories(): void {
$this->cleanupTestData();
// Categories need headers first
SeedHelper::seedCategoryHeaders(null);
SeedHelper::seedDefaultCategories(null);
$qb = $this->db->getQueryBuilder();
$qb->select('slug')
->from('forum_categories');
$result = $qb->executeQuery();
$categories = $result->fetchAll();
$result->closeCursor();
$slugs = array_column($categories, 'slug');
$this->assertContains('general-discussions', $slugs);
$this->assertContains('support', $slugs);
}
/**
* Test that seedDefaultBBCodes creates all default BBCodes
*/
public function testSeedDefaultBBCodesCreatesBBCodes(): void {
$this->cleanupTestData();
SeedHelper::seedDefaultBBCodes(null);
$qb = $this->db->getQueryBuilder();
$qb->select('tag')
->from('forum_bbcodes');
$result = $qb->executeQuery();
$bbcodes = $result->fetchAll();
$result->closeCursor();
$tags = array_column($bbcodes, 'tag');
$this->assertContains('icode', $tags);
$this->assertContains('spoiler', $tags);
$this->assertContains('attachment', $tags);
}
/**
* Test that seeding continues after individual operation failure
* This is crucial for PostgreSQL where transaction abort cascades
*/
public function testSeedingContinuesAfterIndividualFailure(): void {
$this->cleanupTestData();
// Create a partial state - roles exist but categories don't have permissions
SeedHelper::seedDefaultRoles(null);
SeedHelper::seedCategoryHeaders(null);
SeedHelper::seedDefaultCategories(null);
// Skip category permissions intentionally
// Now run full seedAll - it should complete remaining operations
SeedHelper::seedAll(null, false);
// Verify BBCodes were still created despite any issues
$this->assertBBCodesExist();
}
/**
* Test that seedWelcomeThread creates thread and post
*/
public function testSeedWelcomeThreadCreatesContent(): void {
$this->cleanupTestData();
// Welcome thread needs roles, headers, and categories
SeedHelper::seedDefaultRoles(null);
SeedHelper::seedCategoryHeaders(null);
SeedHelper::seedDefaultCategories(null);
SeedHelper::seedWelcomeThread(null);
// Verify thread exists
$qb = $this->db->getQueryBuilder();
$qb->select('id')
->from('forum_threads')
->where($qb->expr()->eq('slug', $qb->createNamedParameter('welcome-to-nextcloud-forums')));
$result = $qb->executeQuery();
$thread = $result->fetch();
$result->closeCursor();
$this->assertNotFalse($thread, 'Welcome thread should exist');
// Verify post exists
$qb = $this->db->getQueryBuilder();
$qb->select('id')
->from('forum_posts')
->where($qb->expr()->eq('thread_id', $qb->createNamedParameter($thread['id'])));
$result = $qb->executeQuery();
$post = $result->fetch();
$result->closeCursor();
$this->assertNotFalse($post, 'Welcome post should exist');
}
/**
* Test that throwOnError parameter works correctly
*/
public function testThrowOnErrorParameter(): void {
$this->cleanupTestData();
// With throwOnError=false, should not throw even if something fails
// (though in a clean state nothing should fail)
SeedHelper::seedAll(null, false);
// Verify it completed
$this->assertRolesExist();
}
/**
* Test database connection recovery between operations
* This specifically tests the PostgreSQL transaction abort fix
*/
public function testConnectionRecoveryBetweenOperations(): void {
$this->cleanupTestData();
// Run seedAll which now includes connection recovery
SeedHelper::seedAll(null, false);
// If we got here without exception, connection recovery worked
// Verify data exists
$this->assertRolesExist();
$this->assertCategoryHeadersExist();
$this->assertCategoriesExist();
$this->assertBBCodesExist();
}
// ========== Helper assertions ==========
private function assertRolesExist(): void {
$qb = $this->db->getQueryBuilder();
$qb->select($qb->func()->count('*', 'count'))
->from('forum_roles');
$result = $qb->executeQuery();
$row = $result->fetch();
$result->closeCursor();
$this->assertGreaterThanOrEqual(4, (int)$row['count'], 'Should have at least 4 roles');
}
private function assertNoDuplicateRoles(): void {
$qb = $this->db->getQueryBuilder();
$qb->select('role_type')
->selectAlias($qb->func()->count('*'), 'count')
->from('forum_roles')
->groupBy('role_type');
$result = $qb->executeQuery();
$groups = $result->fetchAll();
$result->closeCursor();
foreach ($groups as $group) {
$this->assertEquals(1, (int)$group['count'], "Role type '{$group['role_type']}' should not be duplicated");
}
}
private function assertCategoryHeadersExist(): void {
$qb = $this->db->getQueryBuilder();
$qb->select($qb->func()->count('*', 'count'))
->from('forum_cat_headers');
$result = $qb->executeQuery();
$row = $result->fetch();
$result->closeCursor();
$this->assertGreaterThanOrEqual(1, (int)$row['count'], 'Should have at least 1 category header');
}
private function assertCategoriesExist(): void {
$qb = $this->db->getQueryBuilder();
$qb->select($qb->func()->count('*', 'count'))
->from('forum_categories');
$result = $qb->executeQuery();
$row = $result->fetch();
$result->closeCursor();
$this->assertGreaterThanOrEqual(2, (int)$row['count'], 'Should have at least 2 categories');
}
private function assertBBCodesExist(): void {
$qb = $this->db->getQueryBuilder();
$qb->select($qb->func()->count('*', 'count'))
->from('forum_bbcodes');
$result = $qb->executeQuery();
$row = $result->fetch();
$result->closeCursor();
$this->assertGreaterThanOrEqual(3, (int)$row['count'], 'Should have at least 3 BBCodes');
}
private function assertCategoryPermissionsExist(): void {
$qb = $this->db->getQueryBuilder();
$qb->select($qb->func()->count('*', 'count'))
->from('forum_category_perms');
$result = $qb->executeQuery();
$row = $result->fetch();
$result->closeCursor();
$this->assertGreaterThanOrEqual(1, (int)$row['count'], 'Should have at least 1 category permission');
}
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
bootstrap="bootstrap.php"
timeoutForSmallTests="900"
timeoutForMediumTests="900"
timeoutForLargeTests="900"
cacheDirectory=".phpunit.cache"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
colors="true">
<testsuites>
<testsuite name="Forum Integration Tests">
<directory suffix="Test.php">integration</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">../appinfo</directory>
<directory suffix=".php">../lib</directory>
</include>
</source>
</phpunit>

View File

@@ -225,7 +225,7 @@ class CategoryControllerTest extends TestCase {
return $updatedCategory;
});
$response = $this->controller->update($categoryId, $newName);
$response = $this->controller->update($categoryId, null, $newName);
$this->assertEquals(Http::STATUS_OK, $response->getStatus());
$data = $response->getData();
@@ -256,11 +256,37 @@ class CategoryControllerTest extends TestCase {
return $updatedCategory;
});
$response = $this->controller->update($categoryId, $newName, $newDescription, $newSlug, $newSortOrder);
$response = $this->controller->update($categoryId, null, $newName, $newDescription, $newSlug, $newSortOrder);
$this->assertEquals(Http::STATUS_OK, $response->getStatus());
}
public function testUpdateCategoryHeaderId(): void {
$categoryId = 1;
$originalHeaderId = 1;
$newHeaderId = 2;
$category = $this->createCategory($categoryId, $originalHeaderId, 'Test Category');
$this->categoryMapper->expects($this->once())
->method('find')
->with($categoryId)
->willReturn($category);
$this->categoryMapper->expects($this->once())
->method('update')
->willReturnCallback(function ($updatedCategory) use ($newHeaderId) {
$this->assertEquals($newHeaderId, $updatedCategory->getHeaderId());
return $updatedCategory;
});
$response = $this->controller->update($categoryId, $newHeaderId);
$this->assertEquals(Http::STATUS_OK, $response->getStatus());
$data = $response->getData();
$this->assertEquals($newHeaderId, $data['headerId']);
}
public function testUpdateCategoryReturnsNotFoundWhenCategoryDoesNotExist(): void {
$categoryId = 999;
@@ -269,7 +295,7 @@ class CategoryControllerTest extends TestCase {
->with($categoryId)
->willThrowException(new DoesNotExistException('Category not found'));
$response = $this->controller->update($categoryId, 'New Name');
$response = $this->controller->update($categoryId, null, 'New Name');
$this->assertEquals(Http::STATUS_NOT_FOUND, $response->getStatus());
$this->assertEquals(['error' => 'Category not found'], $response->getData());

View File

@@ -20,11 +20,13 @@ use OCA\Forum\Db\ReadMarker;
use OCA\Forum\Db\ReadMarkerMapper;
use OCA\Forum\Db\Thread;
use OCA\Forum\Db\ThreadMapper;
use OCA\Forum\Db\ThreadSubscriptionMapper;
use OCA\Forum\Service\BBCodeService;
use OCA\Forum\Service\NotificationService;
use OCA\Forum\Service\PermissionService;
use OCA\Forum\Service\PostEnrichmentService;
use OCA\Forum\Service\PostHistoryService;
use OCA\Forum\Service\UserPreferencesService;
use OCA\Forum\Service\UserService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
@@ -63,6 +65,10 @@ class PostControllerTest extends TestCase {
private PostHistoryService $postHistoryService;
/** @var UserService&MockObject */
private UserService $userService;
/** @var UserPreferencesService&MockObject */
private UserPreferencesService $userPreferencesService;
/** @var ThreadSubscriptionMapper&MockObject */
private ThreadSubscriptionMapper $threadSubscriptionMapper;
/** @var IUserSession&MockObject */
private IUserSession $userSession;
/** @var LoggerInterface&MockObject */
@@ -85,6 +91,8 @@ class PostControllerTest extends TestCase {
$this->postEnrichmentService = $this->createMock(PostEnrichmentService::class);
$this->postHistoryService = $this->createMock(PostHistoryService::class);
$this->userService = $this->createMock(UserService::class);
$this->userPreferencesService = $this->createMock(UserPreferencesService::class);
$this->threadSubscriptionMapper = $this->createMock(ThreadSubscriptionMapper::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->logger = $this->createMock(LoggerInterface::class);
@@ -113,6 +121,8 @@ class PostControllerTest extends TestCase {
$this->postEnrichmentService,
$this->postHistoryService,
$this->userService,
$this->userPreferencesService,
$this->threadSubscriptionMapper,
$this->userSession,
$this->logger
);

View File

@@ -42,7 +42,7 @@ class UserPreferencesServiceTest extends TestCase {
$userId = 'user1';
// Only config-based preferences (signature is from forum_users)
$this->config->expects($this->exactly(2))
$this->config->expects($this->exactly(3))
->method('getUserValue')
->willReturnCallback(function ($uid, $appId, $key, $default) use ($userId) {
$this->assertEquals($userId, $uid);
@@ -50,6 +50,7 @@ class UserPreferencesServiceTest extends TestCase {
return match ($key) {
UserPreferencesService::PREF_AUTO_SUBSCRIBE_CREATED_THREADS => 'true',
UserPreferencesService::PREF_AUTO_SUBSCRIBE_REPLIED_THREADS => 'false',
UserPreferencesService::PREF_UPLOAD_DIRECTORY => 'Forum',
default => $default,
};
@@ -58,8 +59,9 @@ class UserPreferencesServiceTest extends TestCase {
$result = $this->service->getAllPreferences($userId);
$this->assertIsArray($result);
$this->assertCount(3, $result);
$this->assertCount(4, $result);
$this->assertTrue($result[UserPreferencesService::PREF_AUTO_SUBSCRIBE_CREATED_THREADS]);
$this->assertFalse($result[UserPreferencesService::PREF_AUTO_SUBSCRIBE_REPLIED_THREADS]);
$this->assertEquals('Forum', $result[UserPreferencesService::PREF_UPLOAD_DIRECTORY]);
$this->assertEquals('', $result[UserPreferencesService::PREF_SIGNATURE]);
}
@@ -144,7 +146,7 @@ class UserPreferencesServiceTest extends TestCase {
}
});
$this->config->expects($this->exactly(2))
$this->config->expects($this->exactly(3))
->method('getUserValue')
->willReturnCallback(function ($uid, $appId, $key, $default) use ($userId) {
$this->assertEquals($userId, $uid);
@@ -152,6 +154,7 @@ class UserPreferencesServiceTest extends TestCase {
return match ($key) {
UserPreferencesService::PREF_AUTO_SUBSCRIBE_CREATED_THREADS => 'false',
UserPreferencesService::PREF_AUTO_SUBSCRIBE_REPLIED_THREADS => 'false',
UserPreferencesService::PREF_UPLOAD_DIRECTORY => 'Documents',
default => $default,
};
@@ -160,8 +163,9 @@ class UserPreferencesServiceTest extends TestCase {
$result = $this->service->updatePreferences($userId, $preferences);
$this->assertIsArray($result);
$this->assertCount(3, $result);
$this->assertCount(4, $result);
$this->assertFalse($result[UserPreferencesService::PREF_AUTO_SUBSCRIBE_CREATED_THREADS]);
$this->assertFalse($result[UserPreferencesService::PREF_AUTO_SUBSCRIBE_REPLIED_THREADS]);
$this->assertEquals('Documents', $result[UserPreferencesService::PREF_UPLOAD_DIRECTORY]);
$this->assertEquals('', $result[UserPreferencesService::PREF_SIGNATURE]);
}

View File

@@ -106,16 +106,16 @@
},
{
"name": "php-cs-fixer/shim",
"version": "v3.92.4",
"version": "v3.92.5",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/shim.git",
"reference": "42511441af95955f9d40312f83da870018f0fb10"
"reference": "b13b4ad0a1daa80cf036c70488e86516928a5af0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/42511441af95955f9d40312f83da870018f0fb10",
"reference": "42511441af95955f9d40312f83da870018f0fb10",
"url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/b13b4ad0a1daa80cf036c70488e86516928a5af0",
"reference": "b13b4ad0a1daa80cf036c70488e86516928a5af0",
"shasum": ""
},
"require": {
@@ -152,9 +152,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.92.4"
"source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.92.5"
},
"time": "2026-01-04T00:39:15+00:00"
"time": "2026-01-08T21:58:02+00:00"
}
],
"aliases": [],

View File

@@ -1 +1 @@
0.19.7
0.20.5