diff --git a/.github/workflows/set-backlog-fields.yml b/.github/workflows/set-backlog-fields.yml
index 1035ebf997..c5c803208a 100644
--- a/.github/workflows/set-backlog-fields.yml
+++ b/.github/workflows/set-backlog-fields.yml
@@ -2,7 +2,7 @@ name: Add Issue to Project
on:
issues:
- types: [ opened ]
+ types: [ opened, reopened ]
jobs:
add_issue_to_project:
@@ -30,7 +30,7 @@ jobs:
repository(name:"web-languageforge", owner:$org) {
issue(number: $issue_number) {
id
- projectNextItems(first:100) {
+ projectItems(first:100) {
nodes {
id
}
@@ -39,7 +39,7 @@ jobs:
}
}' -F issue_number=$ISSUE_NUMBER -f org=$ORGANIZATION > project_data.json
- echo 'IN_PROJECT='$(jq '.data.repository.issue.projectNextItems[] | length' project_data.json) >> $GITHUB_ENV
+ echo 'IN_PROJECT='$(jq '.data.repository.issue.projectItems[] | length' project_data.json) >> $GITHUB_ENV
- name: get required info for set operations
env:
@@ -47,24 +47,27 @@ jobs:
ORGANIZATION: sillsdev
run: |
gh api graphql -f query='
- query($org: String!) {
- organization(login: $org){
- projectNext(number: 1) {
+ query ($org: String!) {
+ organization(login: $org) {
+ projectV2(number: 1) {
id
- fields(first:100) {
- nodes {
+ field(name: "ProductOwner") {
+ ... on ProjectV2SingleSelectField {
id
name
- settings
+ options {
+ id
+ name
+ }
}
}
}
}
}' -f org=$ORGANIZATION > project_data.json
- echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
- echo 'FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "ProductOwner") | .id' project_data.json) >> $GITHUB_ENV
- echo 'INCOMING_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "ProductOwner") | .settings | fromjson.options[] | select(.name=="Incoming") | .id' project_data.json) >> $GITHUB_ENV
+ echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
+ echo 'FIELD_ID='$(jq '.data.organization.projectV2.field.id' project_data.json) >> $GITHUB_ENV
+ echo 'INCOMING_ID='$(jq '.data.organization.projectV2.field.options[] | select(.name=="Incoming") | .id' project_data.json) >> $GITHUB_ENV
- name: Add issue to project
if: env.IN_PROJECT == 0
@@ -74,12 +77,12 @@ jobs:
run: |
item_id="$( gh api graphql -f query='
mutation($project:ID!, $item:ID!) {
- addProjectNextItem(input: {projectId: $project, contentId: $item}) {
- projectNextItem {
+ addProjectV2ItemById(input: {projectId: $project, contentId: $item}) {
+ item {
id
}
}
- }' -f project=$PROJECT_ID -f item=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
+ }' -f project=$PROJECT_ID -f item=$ISSUE_ID --jq '.data.addProjectV2ItemById.item.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
@@ -95,13 +98,17 @@ jobs:
$field: ID!
$field_value: String!)
{
- updateProjectNextItemField(input: {
+ updateProjectV2ItemFieldValue(input: {
projectId: $project
itemId: $item
fieldId: $field
- value: $field_value})
+ value: {
+ singleSelectOptionId: $field_value
+ }
+ }
+ )
{
- projectNextItem {
+ projectV2Item {
id
}
}
diff --git a/Makefile b/Makefile
index 4b1a8e02f5..8e67c6e00c 100644
--- a/Makefile
+++ b/Makefile
@@ -46,6 +46,10 @@ unit-tests-ci:
.PHONY: build
build:
npm install
+
+ # ensure we start with a clean ui-dist volume for every build
+ docker volume rm web-languageforge_lf-ui-dist 2>/dev/null
+
docker compose build mail app lfmerge ld-api next-proxy next-app
.PHONY: scan
diff --git a/docker-compose.yml b/docker-compose.yml
index 0f82c07b68..8c66c3dca1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -187,7 +187,7 @@ services:
- SMTP_USERNAME=username
- SMTP_PASSWORD=password
- SERVER_HOSTNAME=nobody.localhost
- command: sh -c "postconf -e 'default_transport = retry:no outbound email allowed' && /run.sh"
+ command: sh -c "postconf -e 'default_transport = discard' && /run.sh"
db:
image: mongo:6
diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile
index 01c37384c3..391c87531d 100644
--- a/docker/app/Dockerfile
+++ b/docker/app/Dockerfile
@@ -38,7 +38,7 @@ RUN npm run build:${NPM_BUILD_SUFFIX}
# COMPOSER-BUILDER
# download composer app dependencies
# git - needed for composer install
-FROM sillsdev/web-languageforge:base-php AS composer-builder
+FROM sillsdev/web-languageforge:base-php-7.4 AS composer-builder
ENV COMPOSER_ALLOW_SUPERUSER=1
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* \
&& install-php-extensions @composer
@@ -47,7 +47,7 @@ COPY src/composer.json src/composer.lock /composer/
RUN composer install
# PRODUCTION IMAGE
-FROM sillsdev/web-languageforge:base-php AS production-app
+FROM sillsdev/web-languageforge:base-php-7.4 AS production-app
RUN rm /usr/local/bin/install-php-extensions
RUN apt-get remove -y gnupg2
RUN mv $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
@@ -55,7 +55,7 @@ RUN mv $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
COPY --from=sillsdev/web-languageforge:wait-latest /wait /wait
# DEVELOPMENT IMAGE
-FROM sillsdev/web-languageforge:base-php AS development-app
+FROM sillsdev/web-languageforge:base-php-7.4 AS development-app
RUN install-php-extensions xdebug-^3.1
COPY docker/app/docker-php-ext-xdebug.ini /usr/local/etc/php/conf.d
RUN mv $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
diff --git a/docker/db/README.md b/docker/db/README.md
index 0e0224b46a..db55d4e083 100644
--- a/docker/db/README.md
+++ b/docker/db/README.md
@@ -32,6 +32,19 @@
`db.users.find()`
+```
+db.users.update(
+ {
+ email: 'admin@example.org'
+ },
+ {
+ $set: {
+ role: 'admin'
+ }
+ }
+)
+```
+
# Test data
## Activity
diff --git a/docker/lfmerge/Dockerfile b/docker/lfmerge/Dockerfile
index 07aed137c8..055ab1e82e 100644
--- a/docker/lfmerge/Dockerfile
+++ b/docker/lfmerge/Dockerfile
@@ -1,2 +1,2 @@
-FROM ghcr.io/sillsdev/lfmerge:2.0.131
+FROM ghcr.io/sillsdev/lfmerge:2.0.133
# Do not add anything to this Dockerfile, it should stay empty
diff --git a/docker/test-php/Dockerfile b/docker/test-php/Dockerfile
index 8ef5761810..b93f83d233 100644
--- a/docker/test-php/Dockerfile
+++ b/docker/test-php/Dockerfile
@@ -1,4 +1,4 @@
-FROM sillsdev/web-languageforge:base-php
+FROM sillsdev/web-languageforge:base-php-7.4
# ----- LINES BELOW COPIED FROM APP DOCKERFILE ----------
diff --git a/next-app/package-lock.json b/next-app/package-lock.json
index c07c3cad25..3a959822fb 100644
--- a/next-app/package-lock.json
+++ b/next-app/package-lock.json
@@ -13,9 +13,9 @@
"@tailwindcss/typography": "^0.5.2",
"autoprefixer": "^10",
"daisyui": "^2",
- "rimraf": "^3",
+ "rimraf": "^4",
"svelte": "^3",
- "svelte-check": "^2",
+ "svelte-check": "^3",
"tailwindcss": "^3",
"tslib": "^2",
"typescript": "^4",
@@ -23,9 +23,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.14.tgz",
- "integrity": "sha512-u0rITLxFIeYAvtJXBQNhNuV4YZe+MD1YvIWT7Nicj8hZAtRVZk2PgNH6KclcKDVHz1ChLKXRfX7d7tkbQBUfrg==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
+ "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
"cpu": [
"arm"
],
@@ -39,9 +39,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.14.tgz",
- "integrity": "sha512-hTqB6Iq13pW4xaydeqQrs8vPntUnMjbkq+PgGiBMi69eYk74naG2ftHWqKnxn874kNrt5Or3rQ0PJutx2doJuQ==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
+ "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
"cpu": [
"arm64"
],
@@ -55,9 +55,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.14.tgz",
- "integrity": "sha512-jir51K4J0K5Rt0KOcippjSNdOl7akKDVz5I6yrqdk4/m9y+rldGptQUF7qU4YpX8U61LtR+w2Tu2Ph+K/UaJOw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
+ "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
"cpu": [
"x64"
],
@@ -71,9 +71,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.14.tgz",
- "integrity": "sha512-vrlaP81IuwPaw1fyX8fHCmivP3Gr73ojVEZy+oWJLAiZVcG8o8Phwun/XDnYIFUHxIoUnMFEpg9o38MIvlw8zw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
+ "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==",
"cpu": [
"arm64"
],
@@ -87,9 +87,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.14.tgz",
- "integrity": "sha512-KV1E01eC2hGYA2qzFDRCK4wdZCRUvMwCNcobgpiiOzp5QXpJBqFPdxI69j8vvzuU7oxFXDgANwEkXvpeQqyOyg==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
+ "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
"cpu": [
"x64"
],
@@ -103,9 +103,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.14.tgz",
- "integrity": "sha512-xRM1RQsazSvL42BNa5XC7ytD4ZDp0ZyJcH7aB0SlYUcHexJUKiDNKR7dlRVlpt6W0DvoRPU2nWK/9/QWS4u2fw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
+ "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
"cpu": [
"arm64"
],
@@ -119,9 +119,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.14.tgz",
- "integrity": "sha512-7ALTAn6YRRf1O6fw9jmn0rWmOx3XfwDo7njGtjy1LXhDGUjTY/vohEPM3ii5MQ411vJv1r498EEx2aBQTJcrEw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
+ "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
"cpu": [
"x64"
],
@@ -135,9 +135,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.14.tgz",
- "integrity": "sha512-X6xULug66ulrr4IzrW7qq+eq9n4MtEyagdWvj4o4cmWr+JXOT47atjpDF9j5M2zHY0UQBmqnHhwl+tXpkpIb2w==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
+ "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
"cpu": [
"arm"
],
@@ -151,9 +151,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.14.tgz",
- "integrity": "sha512-TLh2OcbBUQcMYRH4GbiDkDZfZ4t1A3GgmeXY27dHSI6xrU7IkO00MGBiJySmEV6sH3Wa6pAN6UtaVL0DwkGW4Q==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
+ "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
"cpu": [
"arm64"
],
@@ -167,9 +167,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.14.tgz",
- "integrity": "sha512-oBZkcZ56UZDFCAfE3Fd/Jgy10EoS7Td77NzNGenM+HSY8BkdQAcI9VF9qgwdOLZ+tuftWD7UqZ26SAhtvA3XhA==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
+ "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
"cpu": [
"ia32"
],
@@ -183,9 +183,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.14.tgz",
- "integrity": "sha512-udz/aEHTcuHP+xdWOJmZ5C9RQXHfZd/EhCnTi1Hfay37zH3lBxn/fNs85LA9HlsniFw2zccgcbrrTMKk7Cn1Qg==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
+ "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
"cpu": [
"loong64"
],
@@ -199,9 +199,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.14.tgz",
- "integrity": "sha512-kJ2iEnikUOdC1SiTGbH0fJUgpZwa0ITDTvj9EHf9lm3I0hZ4Yugsb3M6XSl696jVxrEocLe519/8CbSpQWFSrg==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
+ "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
"cpu": [
"mips64el"
],
@@ -215,9 +215,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.14.tgz",
- "integrity": "sha512-kclKxvZvX5YhykwlJ/K9ljiY4THe5vXubXpWmr7q3Zu3WxKnUe1VOZmhkEZlqtnJx31GHPEV4SIG95IqTdfgfg==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
+ "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
"cpu": [
"ppc64"
],
@@ -231,9 +231,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.14.tgz",
- "integrity": "sha512-fdwP9Dc+Kx/cZwp9T9kNqjAE/PQjfrxbio4rZ3XnC3cVvZBjuxpkiyu/tuCwt6SbAK5th6AYNjFdEV9kGC020A==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
+ "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
"cpu": [
"riscv64"
],
@@ -247,9 +247,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.14.tgz",
- "integrity": "sha512-++fw3P4fQk9nqvdzbANRqimKspL8pDCnSpXomyhV7V/ISha/BZIYvZwLBWVKp9CVWKwWPJ4ktsezuLIvlJRHqA==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
+ "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
"cpu": [
"s390x"
],
@@ -263,9 +263,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.14.tgz",
- "integrity": "sha512-TomtswAuzBf2NnddlrS4W01Tv85RM9YtATB3OugY6On0PLM4Ksz5qvQKVAjtzPKoLgL1FiZtfc8mkZc4IgoMEA==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
+ "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==",
"cpu": [
"x64"
],
@@ -279,9 +279,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.14.tgz",
- "integrity": "sha512-U06pfx8P5CqyoPNfqIJmnf+5/r4mJ1S62G4zE6eOjS59naQcxi6GnscUCPH3b+hRG0qdKoGX49RAyiqW+M9aSw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
+ "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
"cpu": [
"x64"
],
@@ -295,9 +295,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.14.tgz",
- "integrity": "sha512-/Jl8XVaWEZNu9rZw+n792GIBupQwHo6GDoapHSb/2xp/Ku28eK6QpR2O9cPBkzHH4OOoMH0LB6zg/qczJ5TTGg==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
+ "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
"cpu": [
"x64"
],
@@ -311,9 +311,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.14.tgz",
- "integrity": "sha512-2iI7D34uTbDn/TaSiUbEHz+fUa8KbN90vX5yYqo12QGpu6T8Jl+kxODsWuMCwoTVlqUpwfPV22nBbFPME9OPtw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
+ "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
"cpu": [
"x64"
],
@@ -327,9 +327,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.14.tgz",
- "integrity": "sha512-SjlM7AHmQVTiGBJE/nqauY1aDh80UBsXZ94g4g60CDkrDMseatiqALVcIuElg4ZSYzJs8hsg5W6zS2zLpZTVgg==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
+ "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
"cpu": [
"arm64"
],
@@ -343,9 +343,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.14.tgz",
- "integrity": "sha512-z06t5zqk8ak0Xom5HG81z2iOQ1hNWYsFQp3sczVLVx+dctWdgl80tNRyTbwjaFfui2vFO12dfE3trCTvA+HO4g==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
+ "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
"cpu": [
"ia32"
],
@@ -359,9 +359,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.14.tgz",
- "integrity": "sha512-ED1UpWcM6lAbalbbQ9TrGqJh4Y9TaASUvu8bI/0mgJcxhSByJ6rbpgqRhxYMaQ682WfA71nxUreaTO7L275zrw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
+ "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
"cpu": [
"x64"
],
@@ -533,9 +533,9 @@
}
},
"node_modules/@sveltejs/adapter-node": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-1.1.0.tgz",
- "integrity": "sha512-br/KlTI24/TFOuQ+SmoTSfwzgWjslGXkSreOYALW8ElVsG8dAh8stDM07hDZjtQQ46r6snd2tev89Sagh8ZOtA==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-1.1.3.tgz",
+ "integrity": "sha512-CXNYbYVAO8PlN95toqQRR6+QaFT8bejShDbmMwJfdokNpscjgxYbS8oAcWqrizxfPTBfyHlW+kDbu5KuWhxUNA==",
"dev": true,
"dependencies": {
"@rollup/plugin-commonjs": "^24.0.0",
@@ -548,16 +548,16 @@
}
},
"node_modules/@sveltejs/kit": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.7.tgz",
- "integrity": "sha512-u8JS4aXFWlrnu/tjl+EhJ/FvBEjLYDyMaLe7EAU4sW+PfDqnqyHBAPg/IQi5JuBg6l+Z816F4WrTe+zplUTQDg==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.1.3.tgz",
+ "integrity": "sha512-LVsJjJmimUf+p26EpBEZ6lDdwk+WqNGPyy9MQ1HVYLgj5AEECATMeg8OlmUujh7bW2Tib0Cag1UWI7zMMe66WA==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@sveltejs/vite-plugin-svelte": "^2.0.0",
"@types/cookie": "^0.5.1",
"cookie": "^0.5.0",
- "devalue": "^4.2.0",
+ "devalue": "^4.2.2",
"esm-env": "^1.0.0",
"kleur": "^4.1.5",
"magic-string": "^0.27.0",
@@ -566,7 +566,7 @@
"set-cookie-parser": "^2.5.1",
"sirv": "^2.0.2",
"tiny-glob": "^0.2.9",
- "undici": "5.14.0"
+ "undici": "5.15.0"
},
"bin": {
"svelte-kit": "svelte-kit.js"
@@ -601,9 +601,9 @@
}
},
"node_modules/@tailwindcss/typography": {
- "version": "0.5.8",
- "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.8.tgz",
- "integrity": "sha512-xGQEp8KXN8Sd8m6R4xYmwxghmswrd0cPnNI2Lc6fmrC3OojysTBJJGSIVwPV56q4t6THFUK3HJ0EaWwpglSxWw==",
+ "version": "0.5.9",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
+ "integrity": "sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==",
"dev": true,
"dependencies": {
"lodash.castarray": "^4.4.0",
@@ -854,9 +854,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001442",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz",
- "integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==",
+ "version": "1.0.30001445",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001445.tgz",
+ "integrity": "sha512-8sdQIdMztYmzfTMO6KfLny878Ln9c2M0fc7EH60IjlP4Dc4PiCy7K2Vl3ITmWgOyPgVQKa5x+UP/KqFsxj4mBg==",
"dev": true,
"funding": [
{
@@ -981,9 +981,9 @@
}
},
"node_modules/daisyui": {
- "version": "2.46.1",
- "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-2.46.1.tgz",
- "integrity": "sha512-i59+nLuzzPAVOhNhot3KLtt6stfYeCIPXs9uiLcpXjykpqxHfBA3W6hQWOUWPMwfqhyQd0WKub3sydtPGjzLtA==",
+ "version": "2.47.0",
+ "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-2.47.0.tgz",
+ "integrity": "sha512-svZpXKldtHjXTEdj/lu2n7b+EQJSatqvmVB59k4dhCDOYUhUZ3jtGuPrgOJlPysHhDjxjCRWWug/fgV5e8tc/w==",
"dev": true,
"dependencies": {
"color": "^4.2",
@@ -1062,9 +1062,9 @@
}
},
"node_modules/devalue": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.2.0.tgz",
- "integrity": "sha512-mbjoAaCL2qogBKgeFxFPOXAUsZchircF+B/79LD4sHH0+NHfYm8gZpQrskKDn5gENGt35+5OI1GUF7hLVnkPDw==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.2.2.tgz",
+ "integrity": "sha512-Pkwd8qrI9O20VJ14fBNHu+on99toTNZFbgWRpZbC0zbDXpnE2WHYcrC1fHhMsF/3Ee+2yaW7vEujAT7fCYgqrA==",
"dev": true
},
"node_modules/didyoumean": {
@@ -1092,9 +1092,9 @@
"dev": true
},
"node_modules/esbuild": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.14.tgz",
- "integrity": "sha512-6xAn3O6ZZyoxZAEkwfI9hw4cEqSr/o1ViJtnkvImVkblmUN65Md04o0S/7H1WNu1XGf1Cjij/on7VO4psIYjkw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz",
+ "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==",
"dev": true,
"hasInstallScript": true,
"bin": {
@@ -1104,28 +1104,28 @@
"node": ">=12"
},
"optionalDependencies": {
- "@esbuild/android-arm": "0.16.14",
- "@esbuild/android-arm64": "0.16.14",
- "@esbuild/android-x64": "0.16.14",
- "@esbuild/darwin-arm64": "0.16.14",
- "@esbuild/darwin-x64": "0.16.14",
- "@esbuild/freebsd-arm64": "0.16.14",
- "@esbuild/freebsd-x64": "0.16.14",
- "@esbuild/linux-arm": "0.16.14",
- "@esbuild/linux-arm64": "0.16.14",
- "@esbuild/linux-ia32": "0.16.14",
- "@esbuild/linux-loong64": "0.16.14",
- "@esbuild/linux-mips64el": "0.16.14",
- "@esbuild/linux-ppc64": "0.16.14",
- "@esbuild/linux-riscv64": "0.16.14",
- "@esbuild/linux-s390x": "0.16.14",
- "@esbuild/linux-x64": "0.16.14",
- "@esbuild/netbsd-x64": "0.16.14",
- "@esbuild/openbsd-x64": "0.16.14",
- "@esbuild/sunos-x64": "0.16.14",
- "@esbuild/win32-arm64": "0.16.14",
- "@esbuild/win32-ia32": "0.16.14",
- "@esbuild/win32-x64": "0.16.14"
+ "@esbuild/android-arm": "0.16.17",
+ "@esbuild/android-arm64": "0.16.17",
+ "@esbuild/android-x64": "0.16.17",
+ "@esbuild/darwin-arm64": "0.16.17",
+ "@esbuild/darwin-x64": "0.16.17",
+ "@esbuild/freebsd-arm64": "0.16.17",
+ "@esbuild/freebsd-x64": "0.16.17",
+ "@esbuild/linux-arm": "0.16.17",
+ "@esbuild/linux-arm64": "0.16.17",
+ "@esbuild/linux-ia32": "0.16.17",
+ "@esbuild/linux-loong64": "0.16.17",
+ "@esbuild/linux-mips64el": "0.16.17",
+ "@esbuild/linux-ppc64": "0.16.17",
+ "@esbuild/linux-riscv64": "0.16.17",
+ "@esbuild/linux-s390x": "0.16.17",
+ "@esbuild/linux-x64": "0.16.17",
+ "@esbuild/netbsd-x64": "0.16.17",
+ "@esbuild/openbsd-x64": "0.16.17",
+ "@esbuild/sunos-x64": "0.16.17",
+ "@esbuild/win32-arm64": "0.16.17",
+ "@esbuild/win32-ia32": "0.16.17",
+ "@esbuild/win32-x64": "0.16.17"
}
},
"node_modules/escalade": {
@@ -1232,9 +1232,9 @@
"dev": true
},
"node_modules/glob": {
- "version": "8.0.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz",
- "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
@@ -1506,9 +1506,9 @@
}
},
"node_modules/minimatch": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz",
- "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==",
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
@@ -1671,9 +1671,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.20",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz",
- "integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==",
+ "version": "8.4.21",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
+ "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
"dev": true,
"funding": [
{
@@ -1887,66 +1887,24 @@
}
},
"node_modules/rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.1.1.tgz",
+ "integrity": "sha512-Z4Y81w8atcvaJuJuBB88VpADRH66okZAuEm+Jtaufa+s7rZmIz+Hik2G53kGaNytE7lsfXyWktTmfVz0H9xuDg==",
"dev": true,
- "dependencies": {
- "glob": "^7.1.3"
- },
"bin": {
- "rimraf": "bin.js"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/rimraf/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/rimraf/node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "dev": true,
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
+ "rimraf": "dist/cjs/src/bin.js"
},
"engines": {
- "node": "*"
+ "node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/rimraf/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/rollup": {
- "version": "3.9.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.9.1.tgz",
- "integrity": "sha512-GswCYHXftN8ZKGVgQhTFUJB/NBXxrRGgO2NCy6E8s1rwEJ4Q9/VttNqcYfEvx4dTo4j58YqdC3OVztPzlKSX8w==",
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.10.0.tgz",
+ "integrity": "sha512-JmRYz44NjC1MjVF2VKxc0M1a97vn+cDxeqWmnwyAF4FvpjK8YFdHpaqvQB+3IxCvX05vJxKZkoMDU8TShhmJVA==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
@@ -2154,34 +2112,34 @@
}
},
"node_modules/svelte": {
- "version": "3.55.0",
- "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.0.tgz",
- "integrity": "sha512-uGu2FVMlOuey4JoKHKrpZFkoYyj0VLjJdz47zX5+gVK5odxHM40RVhar9/iK2YFRVxvfg9FkhfVlR0sjeIrOiA==",
+ "version": "3.55.1",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz",
+ "integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/svelte-check": {
- "version": "2.10.3",
- "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-2.10.3.tgz",
- "integrity": "sha512-Nt1aWHTOKFReBpmJ1vPug0aGysqPwJh2seM1OvICfM2oeyaA62mOiy5EvkXhltGfhCcIQcq2LoE0l1CwcWPjlw==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.0.2.tgz",
+ "integrity": "sha512-DkhKhV0Jt0gh7q9DBB26+J2Vfb9y4/4JWxnbkXBZha7542LOhwvj3edJFjyJ+xjdaXyInZ+YRRYc3V6wytP2ew==",
"dev": true,
"dependencies": {
- "@jridgewell/trace-mapping": "^0.3.9",
+ "@jridgewell/trace-mapping": "^0.3.17",
"chokidar": "^3.4.1",
"fast-glob": "^3.2.7",
"import-fresh": "^3.2.1",
"picocolors": "^1.0.0",
"sade": "^1.7.4",
- "svelte-preprocess": "^4.0.0",
- "typescript": "*"
+ "svelte-preprocess": "^5.0.0",
+ "typescript": "^4.9.4"
},
"bin": {
"svelte-check": "bin/svelte-check"
},
"peerDependencies": {
- "svelte": "^3.24.0"
+ "svelte": "^3.55.0"
}
},
"node_modules/svelte-hmr": {
@@ -2197,21 +2155,21 @@
}
},
"node_modules/svelte-preprocess": {
- "version": "4.10.7",
- "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.7.tgz",
- "integrity": "sha512-sNPBnqYD6FnmdBrUmBCaqS00RyCsCpj2BG58A1JBswNF7b0OKviwxqVrOL/CKyJrLSClrSeqQv5BXNg2RUbPOw==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.0.tgz",
+ "integrity": "sha512-q7lpa7i2FBu8Pa+G0MmuQQWETBwCKgsGmuq1Sf6n8q4uaG9ZLcLP0Y+etC6bF4sE6EbLxfiI38zV6RfPe3RSfg==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
- "@types/pug": "^2.0.4",
- "@types/sass": "^1.16.0",
- "detect-indent": "^6.0.0",
- "magic-string": "^0.25.7",
+ "@types/pug": "^2.0.6",
+ "@types/sass": "^1.43.1",
+ "detect-indent": "^6.1.0",
+ "magic-string": "^0.27.0",
"sorcery": "^0.10.0",
"strip-indent": "^3.0.0"
},
"engines": {
- "node": ">= 9.11.2"
+ "node": ">= 14.10.0"
},
"peerDependencies": {
"@babel/core": "^7.10.2",
@@ -2222,7 +2180,7 @@
"pug": "^3.0.0",
"sass": "^1.26.8",
"stylus": "^0.55.0",
- "sugarss": "^2.0.0",
+ "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0",
"svelte": "^3.23.0",
"typescript": "^3.9.5 || ^4.0.0"
},
@@ -2236,9 +2194,6 @@
"less": {
"optional": true
},
- "node-sass": {
- "optional": true
- },
"postcss": {
"optional": true
},
@@ -2262,15 +2217,6 @@
}
}
},
- "node_modules/svelte-preprocess/node_modules/magic-string": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
- "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
- "dev": true,
- "dependencies": {
- "sourcemap-codec": "^1.4.8"
- }
- },
"node_modules/tailwindcss": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz",
@@ -2375,9 +2321,9 @@
}
},
"node_modules/undici": {
- "version": "5.14.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-5.14.0.tgz",
- "integrity": "sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ==",
+ "version": "5.15.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-5.15.0.tgz",
+ "integrity": "sha512-wCAZJDyjw9Myv+Ay62LAoB+hZLPW9SmKbQkbHIhMw/acKSlpn7WohdMUc/Vd4j1iSMBO0hWwU8mjB7a5p5bl8g==",
"dev": true,
"dependencies": {
"busboy": "^1.6.0"
@@ -2508,156 +2454,156 @@
},
"dependencies": {
"@esbuild/android-arm": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.14.tgz",
- "integrity": "sha512-u0rITLxFIeYAvtJXBQNhNuV4YZe+MD1YvIWT7Nicj8hZAtRVZk2PgNH6KclcKDVHz1ChLKXRfX7d7tkbQBUfrg==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
+ "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
"dev": true,
"optional": true
},
"@esbuild/android-arm64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.14.tgz",
- "integrity": "sha512-hTqB6Iq13pW4xaydeqQrs8vPntUnMjbkq+PgGiBMi69eYk74naG2ftHWqKnxn874kNrt5Or3rQ0PJutx2doJuQ==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
+ "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
"dev": true,
"optional": true
},
"@esbuild/android-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.14.tgz",
- "integrity": "sha512-jir51K4J0K5Rt0KOcippjSNdOl7akKDVz5I6yrqdk4/m9y+rldGptQUF7qU4YpX8U61LtR+w2Tu2Ph+K/UaJOw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
+ "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
"dev": true,
"optional": true
},
"@esbuild/darwin-arm64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.14.tgz",
- "integrity": "sha512-vrlaP81IuwPaw1fyX8fHCmivP3Gr73ojVEZy+oWJLAiZVcG8o8Phwun/XDnYIFUHxIoUnMFEpg9o38MIvlw8zw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
+ "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==",
"dev": true,
"optional": true
},
"@esbuild/darwin-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.14.tgz",
- "integrity": "sha512-KV1E01eC2hGYA2qzFDRCK4wdZCRUvMwCNcobgpiiOzp5QXpJBqFPdxI69j8vvzuU7oxFXDgANwEkXvpeQqyOyg==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
+ "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-arm64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.14.tgz",
- "integrity": "sha512-xRM1RQsazSvL42BNa5XC7ytD4ZDp0ZyJcH7aB0SlYUcHexJUKiDNKR7dlRVlpt6W0DvoRPU2nWK/9/QWS4u2fw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
+ "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.14.tgz",
- "integrity": "sha512-7ALTAn6YRRf1O6fw9jmn0rWmOx3XfwDo7njGtjy1LXhDGUjTY/vohEPM3ii5MQ411vJv1r498EEx2aBQTJcrEw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
+ "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.14.tgz",
- "integrity": "sha512-X6xULug66ulrr4IzrW7qq+eq9n4MtEyagdWvj4o4cmWr+JXOT47atjpDF9j5M2zHY0UQBmqnHhwl+tXpkpIb2w==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
+ "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.14.tgz",
- "integrity": "sha512-TLh2OcbBUQcMYRH4GbiDkDZfZ4t1A3GgmeXY27dHSI6xrU7IkO00MGBiJySmEV6sH3Wa6pAN6UtaVL0DwkGW4Q==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
+ "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
"dev": true,
"optional": true
},
"@esbuild/linux-ia32": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.14.tgz",
- "integrity": "sha512-oBZkcZ56UZDFCAfE3Fd/Jgy10EoS7Td77NzNGenM+HSY8BkdQAcI9VF9qgwdOLZ+tuftWD7UqZ26SAhtvA3XhA==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
+ "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
"dev": true,
"optional": true
},
"@esbuild/linux-loong64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.14.tgz",
- "integrity": "sha512-udz/aEHTcuHP+xdWOJmZ5C9RQXHfZd/EhCnTi1Hfay37zH3lBxn/fNs85LA9HlsniFw2zccgcbrrTMKk7Cn1Qg==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
+ "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-mips64el": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.14.tgz",
- "integrity": "sha512-kJ2iEnikUOdC1SiTGbH0fJUgpZwa0ITDTvj9EHf9lm3I0hZ4Yugsb3M6XSl696jVxrEocLe519/8CbSpQWFSrg==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
+ "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
"dev": true,
"optional": true
},
"@esbuild/linux-ppc64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.14.tgz",
- "integrity": "sha512-kclKxvZvX5YhykwlJ/K9ljiY4THe5vXubXpWmr7q3Zu3WxKnUe1VOZmhkEZlqtnJx31GHPEV4SIG95IqTdfgfg==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
+ "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
"dev": true,
"optional": true
},
"@esbuild/linux-riscv64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.14.tgz",
- "integrity": "sha512-fdwP9Dc+Kx/cZwp9T9kNqjAE/PQjfrxbio4rZ3XnC3cVvZBjuxpkiyu/tuCwt6SbAK5th6AYNjFdEV9kGC020A==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
+ "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
"dev": true,
"optional": true
},
"@esbuild/linux-s390x": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.14.tgz",
- "integrity": "sha512-++fw3P4fQk9nqvdzbANRqimKspL8pDCnSpXomyhV7V/ISha/BZIYvZwLBWVKp9CVWKwWPJ4ktsezuLIvlJRHqA==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
+ "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
"dev": true,
"optional": true
},
"@esbuild/linux-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.14.tgz",
- "integrity": "sha512-TomtswAuzBf2NnddlrS4W01Tv85RM9YtATB3OugY6On0PLM4Ksz5qvQKVAjtzPKoLgL1FiZtfc8mkZc4IgoMEA==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
+ "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==",
"dev": true,
"optional": true
},
"@esbuild/netbsd-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.14.tgz",
- "integrity": "sha512-U06pfx8P5CqyoPNfqIJmnf+5/r4mJ1S62G4zE6eOjS59naQcxi6GnscUCPH3b+hRG0qdKoGX49RAyiqW+M9aSw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
+ "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
"dev": true,
"optional": true
},
"@esbuild/openbsd-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.14.tgz",
- "integrity": "sha512-/Jl8XVaWEZNu9rZw+n792GIBupQwHo6GDoapHSb/2xp/Ku28eK6QpR2O9cPBkzHH4OOoMH0LB6zg/qczJ5TTGg==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
+ "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
"dev": true,
"optional": true
},
"@esbuild/sunos-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.14.tgz",
- "integrity": "sha512-2iI7D34uTbDn/TaSiUbEHz+fUa8KbN90vX5yYqo12QGpu6T8Jl+kxODsWuMCwoTVlqUpwfPV22nBbFPME9OPtw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
+ "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
"dev": true,
"optional": true
},
"@esbuild/win32-arm64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.14.tgz",
- "integrity": "sha512-SjlM7AHmQVTiGBJE/nqauY1aDh80UBsXZ94g4g60CDkrDMseatiqALVcIuElg4ZSYzJs8hsg5W6zS2zLpZTVgg==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
+ "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
"dev": true,
"optional": true
},
"@esbuild/win32-ia32": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.14.tgz",
- "integrity": "sha512-z06t5zqk8ak0Xom5HG81z2iOQ1hNWYsFQp3sczVLVx+dctWdgl80tNRyTbwjaFfui2vFO12dfE3trCTvA+HO4g==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
+ "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
"dev": true,
"optional": true
},
"@esbuild/win32-x64": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.14.tgz",
- "integrity": "sha512-ED1UpWcM6lAbalbbQ9TrGqJh4Y9TaASUvu8bI/0mgJcxhSByJ6rbpgqRhxYMaQ682WfA71nxUreaTO7L275zrw==",
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
+ "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
"dev": true,
"optional": true
},
@@ -2764,9 +2710,9 @@
}
},
"@sveltejs/adapter-node": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-1.1.0.tgz",
- "integrity": "sha512-br/KlTI24/TFOuQ+SmoTSfwzgWjslGXkSreOYALW8ElVsG8dAh8stDM07hDZjtQQ46r6snd2tev89Sagh8ZOtA==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-1.1.3.tgz",
+ "integrity": "sha512-CXNYbYVAO8PlN95toqQRR6+QaFT8bejShDbmMwJfdokNpscjgxYbS8oAcWqrizxfPTBfyHlW+kDbu5KuWhxUNA==",
"dev": true,
"requires": {
"@rollup/plugin-commonjs": "^24.0.0",
@@ -2776,15 +2722,15 @@
}
},
"@sveltejs/kit": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.7.tgz",
- "integrity": "sha512-u8JS4aXFWlrnu/tjl+EhJ/FvBEjLYDyMaLe7EAU4sW+PfDqnqyHBAPg/IQi5JuBg6l+Z816F4WrTe+zplUTQDg==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.1.3.tgz",
+ "integrity": "sha512-LVsJjJmimUf+p26EpBEZ6lDdwk+WqNGPyy9MQ1HVYLgj5AEECATMeg8OlmUujh7bW2Tib0Cag1UWI7zMMe66WA==",
"dev": true,
"requires": {
"@sveltejs/vite-plugin-svelte": "^2.0.0",
"@types/cookie": "^0.5.1",
"cookie": "^0.5.0",
- "devalue": "^4.2.0",
+ "devalue": "^4.2.2",
"esm-env": "^1.0.0",
"kleur": "^4.1.5",
"magic-string": "^0.27.0",
@@ -2793,7 +2739,7 @@
"set-cookie-parser": "^2.5.1",
"sirv": "^2.0.2",
"tiny-glob": "^0.2.9",
- "undici": "5.14.0"
+ "undici": "5.15.0"
}
},
"@sveltejs/vite-plugin-svelte": {
@@ -2811,9 +2757,9 @@
}
},
"@tailwindcss/typography": {
- "version": "0.5.8",
- "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.8.tgz",
- "integrity": "sha512-xGQEp8KXN8Sd8m6R4xYmwxghmswrd0cPnNI2Lc6fmrC3OojysTBJJGSIVwPV56q4t6THFUK3HJ0EaWwpglSxWw==",
+ "version": "0.5.9",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
+ "integrity": "sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==",
"dev": true,
"requires": {
"lodash.castarray": "^4.4.0",
@@ -2990,9 +2936,9 @@
"dev": true
},
"caniuse-lite": {
- "version": "1.0.30001442",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz",
- "integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==",
+ "version": "1.0.30001445",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001445.tgz",
+ "integrity": "sha512-8sdQIdMztYmzfTMO6KfLny878Ln9c2M0fc7EH60IjlP4Dc4PiCy7K2Vl3ITmWgOyPgVQKa5x+UP/KqFsxj4mBg==",
"dev": true
},
"chokidar": {
@@ -3081,9 +3027,9 @@
"dev": true
},
"daisyui": {
- "version": "2.46.1",
- "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-2.46.1.tgz",
- "integrity": "sha512-i59+nLuzzPAVOhNhot3KLtt6stfYeCIPXs9uiLcpXjykpqxHfBA3W6hQWOUWPMwfqhyQd0WKub3sydtPGjzLtA==",
+ "version": "2.47.0",
+ "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-2.47.0.tgz",
+ "integrity": "sha512-svZpXKldtHjXTEdj/lu2n7b+EQJSatqvmVB59k4dhCDOYUhUZ3jtGuPrgOJlPysHhDjxjCRWWug/fgV5e8tc/w==",
"dev": true,
"requires": {
"color": "^4.2",
@@ -3131,9 +3077,9 @@
}
},
"devalue": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.2.0.tgz",
- "integrity": "sha512-mbjoAaCL2qogBKgeFxFPOXAUsZchircF+B/79LD4sHH0+NHfYm8gZpQrskKDn5gENGt35+5OI1GUF7hLVnkPDw==",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.2.2.tgz",
+ "integrity": "sha512-Pkwd8qrI9O20VJ14fBNHu+on99toTNZFbgWRpZbC0zbDXpnE2WHYcrC1fHhMsF/3Ee+2yaW7vEujAT7fCYgqrA==",
"dev": true
},
"didyoumean": {
@@ -3161,33 +3107,33 @@
"dev": true
},
"esbuild": {
- "version": "0.16.14",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.14.tgz",
- "integrity": "sha512-6xAn3O6ZZyoxZAEkwfI9hw4cEqSr/o1ViJtnkvImVkblmUN65Md04o0S/7H1WNu1XGf1Cjij/on7VO4psIYjkw==",
- "dev": true,
- "requires": {
- "@esbuild/android-arm": "0.16.14",
- "@esbuild/android-arm64": "0.16.14",
- "@esbuild/android-x64": "0.16.14",
- "@esbuild/darwin-arm64": "0.16.14",
- "@esbuild/darwin-x64": "0.16.14",
- "@esbuild/freebsd-arm64": "0.16.14",
- "@esbuild/freebsd-x64": "0.16.14",
- "@esbuild/linux-arm": "0.16.14",
- "@esbuild/linux-arm64": "0.16.14",
- "@esbuild/linux-ia32": "0.16.14",
- "@esbuild/linux-loong64": "0.16.14",
- "@esbuild/linux-mips64el": "0.16.14",
- "@esbuild/linux-ppc64": "0.16.14",
- "@esbuild/linux-riscv64": "0.16.14",
- "@esbuild/linux-s390x": "0.16.14",
- "@esbuild/linux-x64": "0.16.14",
- "@esbuild/netbsd-x64": "0.16.14",
- "@esbuild/openbsd-x64": "0.16.14",
- "@esbuild/sunos-x64": "0.16.14",
- "@esbuild/win32-arm64": "0.16.14",
- "@esbuild/win32-ia32": "0.16.14",
- "@esbuild/win32-x64": "0.16.14"
+ "version": "0.16.17",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz",
+ "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==",
+ "dev": true,
+ "requires": {
+ "@esbuild/android-arm": "0.16.17",
+ "@esbuild/android-arm64": "0.16.17",
+ "@esbuild/android-x64": "0.16.17",
+ "@esbuild/darwin-arm64": "0.16.17",
+ "@esbuild/darwin-x64": "0.16.17",
+ "@esbuild/freebsd-arm64": "0.16.17",
+ "@esbuild/freebsd-x64": "0.16.17",
+ "@esbuild/linux-arm": "0.16.17",
+ "@esbuild/linux-arm64": "0.16.17",
+ "@esbuild/linux-ia32": "0.16.17",
+ "@esbuild/linux-loong64": "0.16.17",
+ "@esbuild/linux-mips64el": "0.16.17",
+ "@esbuild/linux-ppc64": "0.16.17",
+ "@esbuild/linux-riscv64": "0.16.17",
+ "@esbuild/linux-s390x": "0.16.17",
+ "@esbuild/linux-x64": "0.16.17",
+ "@esbuild/netbsd-x64": "0.16.17",
+ "@esbuild/openbsd-x64": "0.16.17",
+ "@esbuild/sunos-x64": "0.16.17",
+ "@esbuild/win32-arm64": "0.16.17",
+ "@esbuild/win32-ia32": "0.16.17",
+ "@esbuild/win32-x64": "0.16.17"
}
},
"escalade": {
@@ -3271,9 +3217,9 @@
"dev": true
},
"glob": {
- "version": "8.0.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz",
- "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==",
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@@ -3482,9 +3428,9 @@
"dev": true
},
"minimatch": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz",
- "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==",
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dev": true,
"requires": {
"brace-expansion": "^2.0.1"
@@ -3602,9 +3548,9 @@
"dev": true
},
"postcss": {
- "version": "8.4.20",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz",
- "integrity": "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==",
+ "version": "8.4.21",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
+ "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",
@@ -3721,53 +3667,15 @@
"dev": true
},
"rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "dev": true,
- "requires": {
- "glob": "^7.1.3"
- },
- "dependencies": {
- "brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "requires": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "dev": true,
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- },
- "minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- }
- }
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.1.1.tgz",
+ "integrity": "sha512-Z4Y81w8atcvaJuJuBB88VpADRH66okZAuEm+Jtaufa+s7rZmIz+Hik2G53kGaNytE7lsfXyWktTmfVz0H9xuDg==",
+ "dev": true
},
"rollup": {
- "version": "3.9.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.9.1.tgz",
- "integrity": "sha512-GswCYHXftN8ZKGVgQhTFUJB/NBXxrRGgO2NCy6E8s1rwEJ4Q9/VttNqcYfEvx4dTo4j58YqdC3OVztPzlKSX8w==",
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.10.0.tgz",
+ "integrity": "sha512-JmRYz44NjC1MjVF2VKxc0M1a97vn+cDxeqWmnwyAF4FvpjK8YFdHpaqvQB+3IxCvX05vJxKZkoMDU8TShhmJVA==",
"dev": true,
"requires": {
"fsevents": "~2.3.2"
@@ -3919,25 +3827,25 @@
"dev": true
},
"svelte": {
- "version": "3.55.0",
- "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.0.tgz",
- "integrity": "sha512-uGu2FVMlOuey4JoKHKrpZFkoYyj0VLjJdz47zX5+gVK5odxHM40RVhar9/iK2YFRVxvfg9FkhfVlR0sjeIrOiA==",
+ "version": "3.55.1",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz",
+ "integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==",
"dev": true
},
"svelte-check": {
- "version": "2.10.3",
- "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-2.10.3.tgz",
- "integrity": "sha512-Nt1aWHTOKFReBpmJ1vPug0aGysqPwJh2seM1OvICfM2oeyaA62mOiy5EvkXhltGfhCcIQcq2LoE0l1CwcWPjlw==",
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.0.2.tgz",
+ "integrity": "sha512-DkhKhV0Jt0gh7q9DBB26+J2Vfb9y4/4JWxnbkXBZha7542LOhwvj3edJFjyJ+xjdaXyInZ+YRRYc3V6wytP2ew==",
"dev": true,
"requires": {
- "@jridgewell/trace-mapping": "^0.3.9",
+ "@jridgewell/trace-mapping": "^0.3.17",
"chokidar": "^3.4.1",
"fast-glob": "^3.2.7",
"import-fresh": "^3.2.1",
"picocolors": "^1.0.0",
"sade": "^1.7.4",
- "svelte-preprocess": "^4.0.0",
- "typescript": "*"
+ "svelte-preprocess": "^5.0.0",
+ "typescript": "^4.9.4"
}
},
"svelte-hmr": {
@@ -3948,28 +3856,17 @@
"requires": {}
},
"svelte-preprocess": {
- "version": "4.10.7",
- "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.7.tgz",
- "integrity": "sha512-sNPBnqYD6FnmdBrUmBCaqS00RyCsCpj2BG58A1JBswNF7b0OKviwxqVrOL/CKyJrLSClrSeqQv5BXNg2RUbPOw==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.0.tgz",
+ "integrity": "sha512-q7lpa7i2FBu8Pa+G0MmuQQWETBwCKgsGmuq1Sf6n8q4uaG9ZLcLP0Y+etC6bF4sE6EbLxfiI38zV6RfPe3RSfg==",
"dev": true,
"requires": {
- "@types/pug": "^2.0.4",
- "@types/sass": "^1.16.0",
- "detect-indent": "^6.0.0",
- "magic-string": "^0.25.7",
+ "@types/pug": "^2.0.6",
+ "@types/sass": "^1.43.1",
+ "detect-indent": "^6.1.0",
+ "magic-string": "^0.27.0",
"sorcery": "^0.10.0",
"strip-indent": "^3.0.0"
- },
- "dependencies": {
- "magic-string": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
- "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
- "dev": true,
- "requires": {
- "sourcemap-codec": "^1.4.8"
- }
- }
}
},
"tailwindcss": {
@@ -4052,9 +3949,9 @@
"dev": true
},
"undici": {
- "version": "5.14.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-5.14.0.tgz",
- "integrity": "sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ==",
+ "version": "5.15.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-5.15.0.tgz",
+ "integrity": "sha512-wCAZJDyjw9Myv+Ay62LAoB+hZLPW9SmKbQkbHIhMw/acKSlpn7WohdMUc/Vd4j1iSMBO0hWwU8mjB7a5p5bl8g==",
"dev": true,
"requires": {
"busboy": "^1.6.0"
diff --git a/next-app/package.json b/next-app/package.json
index af9431f409..2f8ae6d86b 100644
--- a/next-app/package.json
+++ b/next-app/package.json
@@ -17,9 +17,9 @@
"@tailwindcss/typography": "^0.5.2",
"autoprefixer": "^10",
"daisyui": "^2",
- "rimraf": "^3",
+ "rimraf": "^4",
"svelte": "^3",
- "svelte-check": "^2",
+ "svelte-check": "^3",
"tailwindcss": "^3",
"tslib": "^2",
"typescript": "^4",
diff --git a/next-app/src/lib/Details.svelte b/next-app/src/lib/Details.svelte
new file mode 100644
index 0000000000..d373c4f8a7
--- /dev/null
+++ b/next-app/src/lib/Details.svelte
@@ -0,0 +1,23 @@
+
+
+
+
+
diff --git a/next-app/src/routes/+page.svelte b/next-app/src/routes/+page.svelte
index 356aa27a56..260c9113b1 100644
--- a/next-app/src/routes/+page.svelte
+++ b/next-app/src/routes/+page.svelte
@@ -1,5 +1,6 @@
-LFNext app
-
-
-
-
- Progress indicator
- start('index.svelte') }>start progress
- stop('index.svelte') }>stop progress
-
-
-
- Error handling
-
- Client
- globalThis.whatIsTheAirspeedVelocityOfAnUnladenSwallow() } danger>Cause run-time error
- await GET({url: '//LFAPP'}) } danger>Cause network error
- {throw Error("sorry, that's not a good password")} } danger>Cause biz logic error
- await GET({url: '//httpbin.org/status/500'}) } danger>Cause backend error
-
- Server
- The change password page has a few options:
-
- Submit the form without supplying anything
- Only supply the password and not the confirm
- Try to submit the form without being authenticated
-
-
-
-
-
-
+
+
+
+
+
LFNext app
+
+
+
+
+ Progress indicator
+ start('index.svelte') }>start progress
+ stop('index.svelte') }>stop progress
+
+
+
+ Error handling
+
+ Client
+ globalThis.whatIsTheAirspeedVelocityOfAnUnladenSwallow() } danger>Cause run-time error
+ await GET({url: '//LFAPP'}) } danger>Cause network error
+ {throw Error("sorry, that's not a good password")} } danger>Cause biz logic error
+ await GET({url: '//httpbin.org/status/500'}) } danger>Cause backend error
+
+ Server
+ The change password page has a few options:
+
+ Submit the form without supplying anything
+ Only supply the password and not the confirm
+ Try to submit the form without being authenticated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/next-app/src/routes/projects/[project_code]/Activity.svelte b/next-app/src/routes/projects/[project_code]/Activity.svelte
index 39574abdbd..94376200dd 100644
--- a/next-app/src/routes/projects/[project_code]/Activity.svelte
+++ b/next-app/src/routes/projects/[project_code]/Activity.svelte
@@ -45,7 +45,7 @@
function byDateThenUser(a: AugmentedActivity, b: AugmentedActivity) {
return a.date_iso === b.date_iso ? a.user === b.user ? des(a.time, b.time)
- : asc(a.user, b.user)
+ : asc(a.user.username, b.user.username)
: des(a.date_iso, b.date_iso)
}
@@ -72,7 +72,7 @@
{#each sorted_activities as activity}
- { activity.user }
+ { activity.user.username }
{ activity.date_locale }
{ action_display[activity.action] || activity.action }
{ activity.entry || '—' }
diff --git a/next-app/src/routes/projects/[project_code]/activities/+server.ts b/next-app/src/routes/projects/[project_code]/activities/+server.ts
index 55bce46c43..717fd51818 100644
--- a/next-app/src/routes/projects/[project_code]/activities/+server.ts
+++ b/next-app/src/routes/projects/[project_code]/activities/+server.ts
@@ -20,6 +20,10 @@ type LegacyActivity = {
id: string,
action: string,
date: string,
+ userRef: {
+ username: string,
+ avatar_ref: string
+ },
content: {
user: string,
entry?: string,
@@ -31,7 +35,7 @@ export type Activity = {
id: string,
action: string,
date: string,
- user: string,
+ user: { username:string, avatar:string },
entry: string,
fields: Field[],
}
@@ -66,12 +70,14 @@ export async function fetch_activities({ cookie, start_date, end_date }: Activit
return activity.map(transform)
}
-function transform({ id, action, date, content }: LegacyActivity): Activity {
+function transform({ id, action, date, content, userRef }: LegacyActivity): Activity {
return {
id,
action,
date,
- user: content.user,
+ user: { username: userRef.username,
+ avatar: userRef.avatar_ref
+ },
entry: content.entry || '',
fields: content.changes || [],
}
diff --git a/src/Api/Library/Shared/Communicate/Email.php b/src/Api/Library/Shared/Communicate/Email.php
index 1199430195..4db70cbab4 100644
--- a/src/Api/Library/Shared/Communicate/Email.php
+++ b/src/Api/Library/Shared/Communicate/Email.php
@@ -16,13 +16,13 @@ class Email
public static function send($from, $to, $subject, $content, $htmlContent = "")
{
// Create the Transport
- $transport = \Swift_SmtpTransport::newInstance(Env::requireEnv("MAIL_HOST"));
+ $transport = new \Swift_SmtpTransport(Env::requireEnv("MAIL_HOST"));
// Create the Mailer using your created Transport
- $mailer = \Swift_Mailer::newInstance($transport);
+ $mailer = new \Swift_Mailer($transport);
// Create a message
- $message = \Swift_Message::newInstance($subject);
+ $message = new \Swift_Message($subject);
$message->setFrom($from);
$message->setTo($to);
$message->setBody($content);
diff --git a/src/Api/Library/Shared/LanguageData.php b/src/Api/Library/Shared/LanguageData.php
index edabdbaef3..f0a796c12b 100644
--- a/src/Api/Library/Shared/LanguageData.php
+++ b/src/Api/Library/Shared/LanguageData.php
@@ -55,7 +55,7 @@ public function read()
$unlisted = new Language("Unlisted Language", "qaa");
$unlisted->country[] = "?";
$unlistedCode = $unlisted->code->three;
- if (!array_key_exists($unlistedCode, $this)) {
+ if (!$this->offsetExists($unlistedCode)) {
$this[$unlistedCode] = $unlisted;
}
}
diff --git a/src/Api/Model/Languageforge/Lexicon/Command/LexEntryCommands.php b/src/Api/Model/Languageforge/Lexicon/Command/LexEntryCommands.php
index 73258773de..b5b4792815 100644
--- a/src/Api/Model/Languageforge/Lexicon/Command/LexEntryCommands.php
+++ b/src/Api/Model/Languageforge/Lexicon/Command/LexEntryCommands.php
@@ -49,7 +49,7 @@ private static function lookupFieldLabel(LexConfigFieldList $fieldList, array $p
foreach ($parts as $part) {
// Strip away anything after a @ character
$fieldName = explode("@", $part, 2)[0];
- if (array_key_exists($fieldName, $currentList->fields)) {
+ if ($currentList->fields->offsetExists($fieldName)) {
/** @var LexConfig $fieldConfig */
$fieldConfig = $currentList->fields[$fieldName];
$result = $fieldConfig->label;
@@ -198,7 +198,7 @@ public static function getEntryLexeme($projectId, $entryId)
$entry = new LexEntryModel($project, $entryId);
$inputSystems = $project->config->entry->fields[LexConfig::LEXEME]->inputSystems;
foreach ($inputSystems as $inputSystem) {
- if (array_key_exists($inputSystem, $entry->lexeme) && !empty($entry->lexeme[$inputSystem])) {
+ if ($entry->lexeme->offsetExists($inputSystem) && !empty($entry->lexeme[$inputSystem])) {
return $entry->lexeme[$inputSystem]->value;
}
}
diff --git a/src/Api/Model/Languageforge/Lexicon/Command/LexEntryDecoder.php b/src/Api/Model/Languageforge/Lexicon/Command/LexEntryDecoder.php
index 2006b19ef9..4e8a1662b3 100644
--- a/src/Api/Model/Languageforge/Lexicon/Command/LexEntryDecoder.php
+++ b/src/Api/Model/Languageforge/Lexicon/Command/LexEntryDecoder.php
@@ -13,13 +13,13 @@ class LexEntryDecoder extends JsonDecoder
* @param array $values A mixed array of JSON (like) data.
* @param string $id
*/
- public static function decode($model, $values, $id = "")
+ public static function decode($model, array $values, $id = "")
{
$decoder = new LexEntryDecoder();
$decoder->_decode($model, $values, $id);
}
- protected function _decode($model, $values, $id)
+ protected function _decode($model, array $values, string $id)
{
switch (get_class($model)) {
case "Api\Model\Languageforge\Lexicon\LexMultiParagraph":
diff --git a/src/Api/Model/Languageforge/Lexicon/Command/LexProjectCommands.php b/src/Api/Model/Languageforge/Lexicon/Command/LexProjectCommands.php
index edb8f1d235..272837a00b 100644
--- a/src/Api/Model/Languageforge/Lexicon/Command/LexProjectCommands.php
+++ b/src/Api/Model/Languageforge/Lexicon/Command/LexProjectCommands.php
@@ -120,7 +120,7 @@ public static function updateCustomFieldViews($projectCode, $customFieldSpecs)
public static function createNewCustomFieldViews($customFieldName, $customFieldType, &$config)
{
foreach ($config->roleViews as $role => $roleView) {
- if (!array_key_exists($customFieldName, $roleView->fields)) {
+ if (!$roleView->fields->offsetExists($customFieldName)) {
if ($customFieldType == "MultiUnicode" || $customFieldType == "MultiString") {
$roleView->fields[$customFieldName] = new LexViewMultiTextFieldConfig();
} else {
@@ -133,7 +133,7 @@ public static function createNewCustomFieldViews($customFieldName, $customFieldT
}
foreach ($config->userViews as $userId => $userView) {
- if (!array_key_exists($customFieldName, $userView->fields)) {
+ if (!$userView->fields->offsetExists($customFieldName)) {
if ($customFieldType == "MultiUnicode" || $customFieldType == "MultiString") {
$userView->fields[$customFieldName] = new LexViewMultiTextFieldConfig();
} else {
@@ -181,7 +181,7 @@ private static function removeDeletedCustomFieldView($customFieldSpecs, &$view)
}
foreach ($customFieldNamesToRemove as $customFieldName) {
- if (array_key_exists($customFieldName, $view->fields)) {
+ if ($view->fields->offsetExists($customFieldName)) {
unset($view->fields[$customFieldName]);
}
}
diff --git a/src/Api/Model/Languageforge/Lexicon/Command/LexUploadCommands.php b/src/Api/Model/Languageforge/Lexicon/Command/LexUploadCommands.php
index ecc1dc7026..a758e60900 100644
--- a/src/Api/Model/Languageforge/Lexicon/Command/LexUploadCommands.php
+++ b/src/Api/Model/Languageforge/Lexicon/Command/LexUploadCommands.php
@@ -101,8 +101,9 @@ public static function uploadAudioFile($projectId, $tmpFilePath)
if (
strcmp($project->whenToConvertAudio, "always") == 0 ||
- (strcmp($project->whenToConvertAudio, "sr") == 0 && // restrictions on send/receive files 12-2022
- (filesize($filePath) > 1000000 ||
+ (strcmp($project->whenToConvertAudio, "sr") == 0 &&
+ // `MaximumFileSize` in https://github.com/sillsdev/chorus/blob/master/src/LibChorus/FileTypeHandlers/audio/AudioFileTypeHandler.cs
+ (filesize($filePath) > 10000000 ||
(strcmp(strtolower($fileExt), ".mp3") !== 0 &&
strcmp(strtolower($fileExt), ".wav") !== 0 &&
strcmp(strtolower($fileExt), ".webm") !== 0)))
diff --git a/src/Api/Model/Languageforge/Lexicon/Config/LexConfig.php b/src/Api/Model/Languageforge/Lexicon/Config/LexConfig.php
index 51f6e268be..875ec77543 100644
--- a/src/Api/Model/Languageforge/Lexicon/Config/LexConfig.php
+++ b/src/Api/Model/Languageforge/Lexicon/Config/LexConfig.php
@@ -93,7 +93,7 @@ public function __construct()
*
* @var array
*/
- private static $flexOptionlistCodes = [
+ private static array $flexOptionlistCodes = [
self::POS => "grammatical-info",
self::SEMDOM => "semantic-domain-ddp4",
self::ENVIRONMENTS => self::ENVIRONMENTS,
diff --git a/src/Api/Model/Languageforge/Lexicon/Config/LexRoleViewConfig.php b/src/Api/Model/Languageforge/Lexicon/Config/LexRoleViewConfig.php
index 4c1a183a0c..6c40d7b1c2 100644
--- a/src/Api/Model/Languageforge/Lexicon/Config/LexRoleViewConfig.php
+++ b/src/Api/Model/Languageforge/Lexicon/Config/LexRoleViewConfig.php
@@ -10,7 +10,7 @@ class LexRoleViewConfig
public function __construct()
{
$this->inputSystems = new ArrayOf();
- $this->fields = new MapOf(function ($data) {
+ $this->fields = new MapOf(function (array $data) {
if (array_key_exists("overrideInputSystems", $data)) {
return new LexViewMultiTextFieldConfig();
} else {
diff --git a/src/Api/Model/Languageforge/Lexicon/Dto/LexDbeDto.php b/src/Api/Model/Languageforge/Lexicon/Dto/LexDbeDto.php
index 6dc78c5808..e20bb969e2 100644
--- a/src/Api/Model/Languageforge/Lexicon/Dto/LexDbeDto.php
+++ b/src/Api/Model/Languageforge/Lexicon/Dto/LexDbeDto.php
@@ -72,9 +72,10 @@ public static function encode($projectId, $userId, $lastFetchTime = null, $offse
}, $deletedCommentsModel->entries);
}
+ // @type ArrayOf
$lexemeInputSystems = $project->config->entry->fields[LexConfig::LEXEME]->inputSystems;
- usort($entries, function ($a, $b) use ($lexemeInputSystems) {
+ usort($entries, function (array $a, array $b) use ($lexemeInputSystems) {
$lexeme1Value = "";
if (array_key_exists(LexConfig::LEXEME, $a)) {
$lexeme1 = $a[LexConfig::LEXEME];
diff --git a/src/Api/Model/Languageforge/Lexicon/Import/LiftDecoder.php b/src/Api/Model/Languageforge/Lexicon/Import/LiftDecoder.php
index 77a3bb2381..5747c9ad1c 100644
--- a/src/Api/Model/Languageforge/Lexicon/Import/LiftDecoder.php
+++ b/src/Api/Model/Languageforge/Lexicon/Import/LiftDecoder.php
@@ -572,7 +572,7 @@ public function isSenseCustomField($nodeId)
* @param string $nodeId
* @return boolean
*/
- public function isExampleCustomField($nodeId)
+ public function isExampleCustomField(string $nodeId)
{
return $this->isCustomField($nodeId, "LexExampleSentence");
}
@@ -584,7 +584,7 @@ public function isExampleCustomField($nodeId)
* @param string $levelClass
* @return boolean
*/
- private function isCustomField($nodeId, $levelClass)
+ private function isCustomField(string $nodeId, string $levelClass)
{
$fieldType = FileUtilities::replaceSpecialCharacters($nodeId);
$customFieldSpecs = $this->getCustomFieldSpecs($fieldType);
@@ -690,7 +690,7 @@ private function addCustomField($sxeNode, $nodeId, $customFieldNamePrefix, $leve
$item->customFields[$customFieldName] = new LexValue();
$item->customFields[$customFieldName]->value((string) $sxeNode["value"]);
} elseif ($customFieldSpecs["Type"] == "ReferenceCollection") {
- if (!array_key_exists($customFieldName, $item->customFields)) {
+ if (!$item->customFields->offsetExists($customFieldName)) {
$item->customFields[$customFieldName] = new LexMultiValue();
}
$item->customFields[$customFieldName]->value((string) $sxeNode["value"]);
@@ -717,7 +717,7 @@ private function createCustomField($fieldType, $customFieldNamePrefix, $customFi
{
$customFieldName = $customFieldNamePrefix . str_replace(" ", "_", $fieldType);
$levelConfig->fieldOrder->ensureValueExists($customFieldName);
- if (!array_key_exists($customFieldName, $levelConfig->fields)) {
+ if (!$levelConfig->fields->offsetExists($customFieldName)) {
if ($customFieldSpecs["Type"] == "ReferenceAtom") {
$levelConfig->fields[$customFieldName] = new LexConfigOptionList();
$levelConfig->fields[$customFieldName]->listCode = $customFieldSpecs["range"];
diff --git a/src/Api/Model/Languageforge/Lexicon/Import/LiftImport.php b/src/Api/Model/Languageforge/Lexicon/Import/LiftImport.php
index d45b17fed0..c9ea369e6f 100644
--- a/src/Api/Model/Languageforge/Lexicon/Import/LiftImport.php
+++ b/src/Api/Model/Languageforge/Lexicon/Import/LiftImport.php
@@ -303,7 +303,7 @@ public static function rangeToOptionList(
$optionList->items->exchangeArray([]);
foreach ($liftRange->rangeElements as $id => $elem) {
- if ($elem->label && array_key_exists($interfaceLang, $elem->label)) {
+ if ($elem->label && $elem->label->offsetExists($interfaceLang)) {
$label = $elem->label[$interfaceLang]->value;
if (isset($elem->abbrev) && isset($elem->abbrev[$interfaceLang])) {
$abbrev = $elem->abbrev[$interfaceLang]->value;
diff --git a/src/Api/Model/Languageforge/Lexicon/LexEntryModel.php b/src/Api/Model/Languageforge/Lexicon/LexEntryModel.php
index 4c221b8a61..f4e8ccdba4 100644
--- a/src/Api/Model/Languageforge/Lexicon/LexEntryModel.php
+++ b/src/Api/Model/Languageforge/Lexicon/LexEntryModel.php
@@ -19,7 +19,7 @@ function generateSense()
return new LexSense();
}
-function generateCustomField($data)
+function generateCustomField(array $data)
{
CodeGuard::checkTypeAndThrow($data, "array");
if (array_key_exists("type", $data)) {
diff --git a/src/Api/Model/Languageforge/Lexicon/LexMultiText.php b/src/Api/Model/Languageforge/Lexicon/LexMultiText.php
index 856a76d419..700fbd780c 100644
--- a/src/Api/Model/Languageforge/Lexicon/LexMultiText.php
+++ b/src/Api/Model/Languageforge/Lexicon/LexMultiText.php
@@ -18,7 +18,7 @@ public function __construct()
public function form($inputSystem, $value)
{
- if (array_key_exists($inputSystem, $this)) {
+ if ($this->offsetExists($inputSystem)) {
$this[$inputSystem]->value($value);
} else {
$this[$inputSystem] = new LexValue($value);
@@ -27,7 +27,7 @@ public function form($inputSystem, $value)
public function appendForm($inputSystem, $value, $separator = "; ")
{
- if (array_key_exists($inputSystem, $this)) {
+ if ($this->offsetExists($inputSystem)) {
$oldValue = $this[$inputSystem]->value;
$newValue = $oldValue . $separator . $value;
$this[$inputSystem]->value($newValue);
@@ -42,7 +42,7 @@ public function appendForm($inputSystem, $value, $separator = "; ")
*/
public function hasForm($inputSystem)
{
- return array_key_exists($inputSystem, $this);
+ return $this->offsetExists($inputSystem);
}
public function differences(LexMultiText $otherMultiText)
diff --git a/src/Api/Model/Languageforge/Lexicon/LexProjectModel.php b/src/Api/Model/Languageforge/Lexicon/LexProjectModel.php
index b09b03c015..76725c8158 100644
--- a/src/Api/Model/Languageforge/Lexicon/LexProjectModel.php
+++ b/src/Api/Model/Languageforge/Lexicon/LexProjectModel.php
@@ -71,7 +71,7 @@ public function __construct($id = "")
public function addInputSystem($tag, $abbr = "", $name = "")
{
static $languages = null;
- if (!array_key_exists($tag, $this->inputSystems)) {
+ if (!$this->inputSystems->offsetExists($tag)) {
if (!$abbr) {
$abbr = $tag;
}
@@ -81,7 +81,7 @@ public function addInputSystem($tag, $abbr = "", $name = "")
$languages = new LanguageData();
}
$languageCode = LanguageData::getCode($tag);
- if (array_key_exists($languageCode, $languages)) {
+ if ($languages->offsetExists($languageCode)) {
$name = $languages[$languageCode]->name;
}
}
diff --git a/src/Api/Model/Languageforge/Lexicon/LexSense.php b/src/Api/Model/Languageforge/Lexicon/LexSense.php
index e928cd0cf0..626dbd0343 100644
--- a/src/Api/Model/Languageforge/Lexicon/LexSense.php
+++ b/src/Api/Model/Languageforge/Lexicon/LexSense.php
@@ -230,7 +230,7 @@ public function searchExamplesMultiTextFor($propertyName, $tag, $text)
foreach ($this->examples as $index => $example) {
if (
isset($example->{$propertyName}) &&
- array_key_exists($tag, $example->{$propertyName}) &&
+ $example->{$propertyName}->offsetExists($tag) &&
trim($example->{$propertyName}[$tag]) !== "" &&
$example->{$propertyName}[$tag] == $text
) {
diff --git a/src/Api/Model/Shared/AccessTokenModel.php b/src/Api/Model/Shared/AccessTokenModel.php
deleted file mode 100644
index a6137fd75f..0000000000
--- a/src/Api/Model/Shared/AccessTokenModel.php
+++ /dev/null
@@ -1,15 +0,0 @@
-users)) {
+ if ($project->users->offsetExists($userId)) {
$sessionData["project"] = [];
$sessionData["project"]["id"] = (string) $projectId;
$sessionData["project"]["projectName"] = $project->projectName;
diff --git a/src/Api/Model/Shared/Command/UserCommands.php b/src/Api/Model/Shared/Command/UserCommands.php
index f14d13ca63..15c0d44fa4 100644
--- a/src/Api/Model/Shared/Command/UserCommands.php
+++ b/src/Api/Model/Shared/Command/UserCommands.php
@@ -24,6 +24,7 @@
use Palaso\Utilities\CodeGuard;
use Symfony\Component\HttpFoundation\Session\Session;
use Api\Library\Shared\UrlHelper;
+use Api\Model\Shared\Command\ProjectCommands;
class UserCommands
{
@@ -138,20 +139,70 @@ public static function updateUserProfile($params, $userId, DeliveryInterface $de
* @param array $userIds
* @return int Total number of users removed.
*/
- public static function deleteUsers($userIds)
+ public static function deleteAccounts($userIds, $currentId)
{
CodeGuard::checkTypeAndThrow($userIds, "array");
$count = 0;
foreach ($userIds as $userId) {
CodeGuard::checkTypeAndThrow($userId, "string");
- $userModel = new UserModel($userId);
- $userModel->remove();
+ self::deleteAccount($userId, $currentId);
$count++;
}
return $count;
}
+ /**
+ * @param $userId
+ * @return int 0 or 1 successful removal
+ * @throws \Exception
+ */
+ public static function deleteAccount($userId, $currentUserId)
+ {
+ $user = new UserModelWithPassword($userId);
+ $currentUser = new UserModel($currentUserId);
+
+ // Makes sure this user is not an owner on any projects
+ foreach ($user->projects->refs as $id) {
+ $project = new ProjectModel($id->asString());
+ if ($project->ownerRef->asString() == $userId) {
+ throw new \Exception(
+ "The user '$user->username' owns one or more projects. Before account deletion, this user's projects must either be transfered to new owners or deleted."
+ );
+ }
+ }
+
+ // Makes sure the user doing the action has the right privileges
+ if ($currentUser->role != SystemRoles::SYSTEM_ADMIN && $userId != $currentUserId) {
+ throw new \Exception("The current user does not have sufficient privileges to delete the target account.");
+ }
+
+ // Deactivates account and removes personal information from the user model.
+ // Will now use the user's id instead of name and username when displaying historical activity.
+ $user->active = false;
+ $user->isDeleted = true;
+ $user->password = null;
+ $user->username = "[Deleted User]";
+ $user->name = "[Deleted User]";
+ $user->languageDepotUsername = null;
+ $user->email = null;
+ $user->mobile_phone = null;
+ $user->age = null;
+ $user->gender = null;
+ $user->googleOAuthIds = null;
+ $user->facebookOAuthIds = null;
+
+ $default_avatar = "anonymoose.png";
+ $user->avatar_ref = $default_avatar;
+ $user->write();
+
+ // Removes the user from each project the user used to be in
+ foreach ($user->projects->refs as $projectIdObject) {
+ $projectId = $projectIdObject->asString();
+ ProjectCommands::removeUsers($projectId, [$userId]);
+ }
+ }
+
/**
* @return UserListModel
*/
diff --git a/src/Api/Model/Shared/Dto/ActivityListDto.php b/src/Api/Model/Shared/Dto/ActivityListDto.php
index c6ff68e0f6..b722c0476f 100644
--- a/src/Api/Model/Shared/Dto/ActivityListDto.php
+++ b/src/Api/Model/Shared/Dto/ActivityListDto.php
@@ -367,7 +367,7 @@ private static function prepareActivityContentForEntryDifferences($item, $projec
list($name, $position) = self::splitFieldIdPart($part);
$position = $position + 1; // Mongo stores 0-based indices, but DTO wants 1-based
// $guid not used in this DTO
- if (array_key_exists($name, $currentConfig->fields)) {
+ if ($currentConfig->fields->offsetExists($name)) {
$mostRecentName = $name;
$mostRecentPosition = $position;
// $fieldNameHierarchy[] = $name;
diff --git a/src/Api/Model/Shared/Dto/RightsHelper.php b/src/Api/Model/Shared/Dto/RightsHelper.php
index f699200cd5..1770636994 100644
--- a/src/Api/Model/Shared/Dto/RightsHelper.php
+++ b/src/Api/Model/Shared/Dto/RightsHelper.php
@@ -153,9 +153,6 @@ public function userCanAccessMethod($methodName)
case "user_create":
return $this->userHasSiteRight(Domain::USERS + Operation::EDIT);
- case "user_delete":
- return $this->userHasSiteRight(Domain::USERS + Operation::DELETE);
-
case "project_archive":
return $this->userHasSiteRight(Domain::PROJECTS + Operation::ARCHIVE) ||
$this->userHasSiteRight(Domain::PROJECTS + Operation::CREATE);
@@ -189,6 +186,7 @@ public function userCanAccessMethod($methodName)
return $this->userHasSiteRight(Domain::USERS + Operation::VIEW_OWN);
case "user_updateProfile":
+ case "user_delete":
case "check_unique_identity":
case "change_password": // change_password requires additional protection in the method itself
return $this->userHasSiteRight(Domain::USERS + Operation::EDIT_OWN);
diff --git a/src/Api/Model/Shared/Mapper/JsonDecoder.php b/src/Api/Model/Shared/Mapper/JsonDecoder.php
index 8f8758e4e9..554c75c143 100644
--- a/src/Api/Model/Shared/Mapper/JsonDecoder.php
+++ b/src/Api/Model/Shared/Mapper/JsonDecoder.php
@@ -23,7 +23,7 @@ public static function is_assoc($array)
* @param string $id
* @throws \Exception
*/
- public static function decode($model, $values, $id = "")
+ public static function decode($model, array $values, $id = "")
{
$decoder = new JsonDecoder();
$decoder->_decode($model, $values, $id);
@@ -36,7 +36,7 @@ public static function decode($model, $values, $id = "")
* @param string $id
* @throws \Exception
*/
- protected function _decode($model, $values, $id)
+ protected function _decode($model, array $values, string $id)
{
CodeGuard::checkTypeAndThrow($values, "array");
$propertiesToIgnore = $this->getPrivateAndReadOnlyProperties($model);
diff --git a/src/Api/Model/Shared/Mapper/JsonEncoder.php b/src/Api/Model/Shared/Mapper/JsonEncoder.php
index 8b4f412f9e..1fbc93f158 100644
--- a/src/Api/Model/Shared/Mapper/JsonEncoder.php
+++ b/src/Api/Model/Shared/Mapper/JsonEncoder.php
@@ -28,47 +28,54 @@ public static function encode($model)
protected function _encode($model)
{
$data = [];
- $properties = get_object_vars($model);
- $privateProperties = [];
- if (method_exists($model, "getPrivateProperties")) {
- $privateProperties = (array) $model->getPrivateProperties();
- }
- foreach ($properties as $key => $value) {
- if (in_array($key, $privateProperties)) {
- continue;
+ if (is_a($model, "Api\Model\Shared\Mapper\ArrayOf")) {
+ $data = $this->encodeArrayOf("", $model);
+ } elseif (is_a($model, "Api\Model\Shared\Mapper\MapOf")) {
+ $data = $this->encodeMapOf("", $model);
+ } else {
+ $properties = get_object_vars($model);
+ $privateProperties = [];
+ if (method_exists($model, "getPrivateProperties")) {
+ $privateProperties = (array) $model->getPrivateProperties();
}
- if (is_a($value, "Api\Model\Shared\Mapper\IdReference")) {
- $data[$key] = $this->encodeIdReference($key, $model->{$key});
- } elseif (is_a($value, "Api\Model\Shared\Mapper\Id")) {
- $data[$key] = $this->encodeId($key, $model->{$key});
- } elseif (is_a($value, "Api\Model\Shared\Mapper\ArrayOf")) {
- $data[$key] = $this->encodeArrayOf($key, $model->{$key});
- } elseif (is_a($value, "Api\Model\Shared\Mapper\MapOf")) {
- $data[$key] = $this->encodeMapOf($key, $model->{$key});
- } elseif (is_a($value, "DateTime")) {
- $data[$key] = $this->encodeDateTime($model->{$key});
- } elseif (is_a($value, "Litipk\Jiffy\UniversalTimestamp")) {
- $data[$key] = $this->encodeUniversalTimestamp($model->{$key});
- } elseif (is_a($value, "Api\Model\Shared\Mapper\ReferenceList")) {
- $data[$key] = $this->encodeReferenceList($key, $model->{$key});
- } else {
- // Data type protection
- if (is_array($value)) {
- throw new \Exception("Must not encode array in '" . get_class($model) . "->" . $key . "'");
- }
- // Special hack to help debugging our app
- if ($key == "projects" || $key == "users") {
- throw new \Exception("Possible bad write of '$key'\n" . var_export($model, true));
+
+ foreach ($properties as $key => $value) {
+ if (in_array($key, $privateProperties)) {
+ continue;
}
- if (is_object($value)) {
- $data[$key] = $this->_encode($value);
+ if (is_a($value, "Api\Model\Shared\Mapper\IdReference")) {
+ $data[$key] = $this->encodeIdReference($key, $model->{$key});
+ } elseif (is_a($value, "Api\Model\Shared\Mapper\Id")) {
+ $data[$key] = $this->encodeId($key, $model->{$key});
+ } elseif (is_a($value, "Api\Model\Shared\Mapper\ArrayOf")) {
+ $data[$key] = $this->encodeArrayOf($key, $model->{$key});
+ } elseif (is_a($value, "Api\Model\Shared\Mapper\MapOf")) {
+ $data[$key] = $this->encodeMapOf($key, $model->{$key});
+ } elseif (is_a($value, "DateTime")) {
+ $data[$key] = $this->encodeDateTime($model->{$key});
+ } elseif (is_a($value, "Litipk\Jiffy\UniversalTimestamp")) {
+ $data[$key] = $this->encodeUniversalTimestamp($model->{$key});
+ } elseif (is_a($value, "Api\Model\Shared\Mapper\ReferenceList")) {
+ $data[$key] = $this->encodeReferenceList($key, $model->{$key});
} else {
- // Default encode
- if (is_null($value)) {
- $value = "";
+ // Data type protection
+ if (is_array($value)) {
+ throw new \Exception("Must not encode array in '" . get_class($model) . "->" . $key . "'");
+ }
+ // Special hack to help debugging our app
+ if ($key == "projects" || $key == "users") {
+ throw new \Exception("Possible bad write of '$key'\n" . var_export($model, true));
+ }
+ if (is_object($value)) {
+ $data[$key] = $this->_encode($value);
+ } else {
+ // Default encode
+ if (is_null($value)) {
+ $value = "";
+ }
+ $data[$key] = $value;
}
- $data[$key] = $value;
}
}
}
diff --git a/src/Api/Model/Shared/Mapper/MapOf.php b/src/Api/Model/Shared/Mapper/MapOf.php
index a89b103786..a756ff0799 100644
--- a/src/Api/Model/Shared/Mapper/MapOf.php
+++ b/src/Api/Model/Shared/Mapper/MapOf.php
@@ -35,7 +35,6 @@ public function hasGenerator()
public function offsetGet($index)
{
CodeGuard::checkTypeAndThrow($index, "string");
-
return parent::offsetGet($index);
}
@@ -44,4 +43,10 @@ public function offsetSet($index, $newval)
CodeGuard::checkTypeAndThrow($index, "string");
parent::offsetSet($index, $newval);
}
+
+ public function offsetExists($index)
+ {
+ CodeGuard::checkTypeAndThrow($index, "string");
+ return parent::offsetExists($index);
+ }
}
diff --git a/src/Api/Model/Shared/Mapper/MongoEncoder.php b/src/Api/Model/Shared/Mapper/MongoEncoder.php
index 0b58b5eae3..f9979df959 100644
--- a/src/Api/Model/Shared/Mapper/MongoEncoder.php
+++ b/src/Api/Model/Shared/Mapper/MongoEncoder.php
@@ -26,37 +26,43 @@ public static function encode($model)
* @return array
* @throws \Exception
*/
- protected function _encode($model, $encodeId = false)
+ protected function _encode(object $model, $encodeId = false)
{
$data = [];
- $properties = get_object_vars($model);
- foreach ($properties as $key => $value) {
- if (is_a($value, "Api\Model\Shared\Mapper\ArrayOf")) {
- $data[$key] = $this->encodeArrayOf($model->{$key});
- } elseif (is_a($value, "Api\Model\Shared\Mapper\MapOf")) {
- $data[$key] = $this->encodeMapOf($model->{$key});
- } elseif (is_a($value, "Api\Model\Shared\Mapper\IdReference")) {
- $data[$key] = $this->encodeIdReference($model->{$key});
- } elseif (is_a($value, "Api\Model\Shared\Mapper\Id")) {
- if ($encodeId) {
- $data[$key] = $this->encodeId($model->{$key});
- }
- } elseif (is_a($value, "DateTime")) {
- $data[$key] = $this->encodeDateTime($model->{$key});
- } elseif (is_a($value, "Litipk\Jiffy\UniversalTimestamp")) {
- $data[$key] = $this->encodeUniversalTimestamp($model->{$key});
- } elseif (is_a($value, "Api\Model\Shared\Mapper\ReferenceList")) {
- $data[$key] = $this->encodeReferenceList($model->{$key});
- } else {
- // Data type protection
- if (is_array($value)) {
- throw new \Exception("Must not encode array in '" . get_class($model) . "->" . $key . "'");
- }
- if (is_object($value)) {
- $data[$key] = $this->_encode($value, true);
+ if (is_a($model, "Api\Model\Shared\Mapper\ArrayOf")) {
+ $data = $this->encodeArrayOf($model);
+ } elseif (is_a($model, "Api\Model\Shared\Mapper\MapOf")) {
+ $data = $this->encodeMapOf($model);
+ } else {
+ $properties = get_object_vars($model);
+ foreach ($properties as $key => $value) {
+ if (is_a($value, "Api\Model\Shared\Mapper\ArrayOf")) {
+ $data[$key] = $this->encodeArrayOf($model->{$key});
+ } elseif (is_a($value, "Api\Model\Shared\Mapper\MapOf")) {
+ $data[$key] = $this->encodeMapOf($model->{$key});
+ } elseif (is_a($value, "Api\Model\Shared\Mapper\IdReference")) {
+ $data[$key] = $this->encodeIdReference($model->{$key});
+ } elseif (is_a($value, "Api\Model\Shared\Mapper\Id")) {
+ if ($encodeId) {
+ $data[$key] = $this->encodeId($model->{$key});
+ }
+ } elseif (is_a($value, "DateTime")) {
+ $data[$key] = $this->encodeDateTime($model->{$key});
+ } elseif (is_a($value, "Litipk\Jiffy\UniversalTimestamp")) {
+ $data[$key] = $this->encodeUniversalTimestamp($model->{$key});
+ } elseif (is_a($value, "Api\Model\Shared\Mapper\ReferenceList")) {
+ $data[$key] = $this->encodeReferenceList($model->{$key});
} else {
- // Default encode
- $data[$key] = $value;
+ // Data type protection
+ if (is_array($value)) {
+ throw new \Exception("Must not encode array in '" . get_class($model) . "->" . $key . "'");
+ }
+ if (is_object($value)) {
+ $data[$key] = $this->_encode($value, true);
+ } else {
+ // Default encode
+ $data[$key] = $value;
+ }
}
}
}
diff --git a/src/Api/Model/Shared/ProjectModel.php b/src/Api/Model/Shared/ProjectModel.php
index 60d341c4f7..66271bdae9 100644
--- a/src/Api/Model/Shared/ProjectModel.php
+++ b/src/Api/Model/Shared/ProjectModel.php
@@ -178,7 +178,7 @@ public function addUserByInviteToken($userId)
*/
public function removeUser($userId)
{
- if (array_key_exists($userId, $this->users)) {
+ if ($this->users->offsetExists($userId)) {
unset($this->users[$userId]);
}
}
@@ -198,7 +198,7 @@ public function listUsers()
$userList->read();
for ($i = 0, $l = count($userList->entries); $i < $l; $i++) {
$userId = $userList->entries[$i]["id"];
- if (!array_key_exists($userId, $this->users)) {
+ if (!$this->users->offsetExists($userId)) {
continue;
}
$userList->entries[$i]["role"] = $this->users[$userId]->role;
@@ -211,7 +211,7 @@ public function listInvitees()
$invitees = new InviteeListProjectModel($this->id->asString());
$invitees->read();
foreach ($invitees->entries as $i => $invitee) {
- if (array_key_exists($invitee["id"], $this->users)) {
+ if ($this->users->offsetExists($invitee["id"])) {
$invitees->entries[$i]["role"] = $this->users[$invitee["id"]]->role;
}
}
diff --git a/src/Api/Model/Shared/UserListModel.php b/src/Api/Model/Shared/UserListModel.php
index d727a5e0eb..e4a4e945f8 100644
--- a/src/Api/Model/Shared/UserListModel.php
+++ b/src/Api/Model/Shared/UserListModel.php
@@ -10,7 +10,7 @@ public function __construct()
{
parent::__construct(
UserModelMongoMapper::instance(),
- ["username" => ['$regex' => ""]],
+ ["username" => ['$regex' => ""], "isDeleted" => false],
["username", "email", "name", "avatar_ref", "role", "projects", "active"]
);
}
diff --git a/src/Api/Model/Shared/UserModel.php b/src/Api/Model/Shared/UserModel.php
index 72aa6c1c88..eb0749208d 100644
--- a/src/Api/Model/Shared/UserModel.php
+++ b/src/Api/Model/Shared/UserModel.php
@@ -57,13 +57,12 @@ public function __construct($id = "")
$this->siteRole = new MapOf();
$this->googleOAuthIds = new ArrayOf();
$this->facebookOAuthIds = new ArrayOf();
- $this->paratextOAuthIds = new ArrayOf();
- $this->paratextAccessToken = new AccessTokenModel();
$this->validationExpirationDate = new \DateTime();
$this->resetPasswordExpirationDate = new \DateTime();
$this->projectsProperties = new MapOf(function () {
return new ProjectProperties();
});
+ $this->isDeleted = false;
/*
* We don't need to set 'role' to ReadOnly because we control where it's modified
@@ -115,9 +114,6 @@ public function __construct($id = "")
/** @var ArrayOf */
public $googleOAuthIds;
- /** @var ArrayOf */
- public $paratextOAuthIds;
-
/** @var ArrayOf */
public $facebookOAuthIds;
@@ -130,14 +126,9 @@ public function __construct($id = "")
/** @var boolean */
public $isInvited;
- //public $groups;
-
/** @var ReferenceList */
public $projects;
- /** @var AccessTokenModel */
- public $paratextAccessToken;
-
/*
* User Profile accessible
*/
@@ -162,6 +153,9 @@ public function __construct($id = "")
/** @var string */
public $gender;
+ /** @var boolean */
+ public $isDeleted;
+
/**
* @var int timestamp, see time()
*/
@@ -200,21 +194,6 @@ public function setProperties($properties, $params)
}
}
- /**
- * Removes a user from the collection
- * Project references to this user are also removed
- */
- public function remove()
- {
- foreach ($this->projects->refs as $id) {
- /* @var Id $id */
- $project = new ProjectModel($id->asString());
- $project->removeUser($this->id->asString());
- $project->write();
- }
- UserModelMongoMapper::instance()->remove($this->id->asString());
- }
-
/**
* @param string $projectId
* @return bool
diff --git a/src/Api/Service/Sf.php b/src/Api/Service/Sf.php
index 3e04628180..d3ae5bc22c 100644
--- a/src/Api/Service/Sf.php
+++ b/src/Api/Service/Sf.php
@@ -18,7 +18,6 @@
use Api\Model\Shared\Command\SessionCommands;
use Api\Model\Shared\Command\UserCommands;
use Api\Model\Shared\Command\LdapiCommands;
-use Api\Model\Shared\Communicate\EmailSettings;
use Api\Model\Shared\Dto\ActivityListDto;
use Api\Model\Shared\Dto\ProjectInsightsDto;
use Api\Model\Shared\Dto\ProjectListDto;
@@ -144,7 +143,11 @@ public function user_updateProfile($params)
*/
public function user_delete($userIds)
{
- return UserCommands::deleteUsers($userIds);
+ $result = UserCommands::deleteAccounts($userIds, $this->userId);
+ if (in_array($this->userId, $userIds)) {
+ $this->app["session"]->getFlashBag()->add("infoMessage", "Your account was permanently deleted");
+ }
+ return $result;
}
/**
diff --git a/src/Site/OAuth/SelectAccountAuthorizationParametersTrait.php b/src/Site/OAuth/SelectAccountAuthorizationParametersTrait.php
index 38b5490e23..f55abd3606 100644
--- a/src/Site/OAuth/SelectAccountAuthorizationParametersTrait.php
+++ b/src/Site/OAuth/SelectAccountAuthorizationParametersTrait.php
@@ -7,7 +7,7 @@
*/
trait SelectAccountAuthorizationParametersTrait
{
- protected function getAuthorizationParameters(array $options)
+ protected function getAuthorizationParameters(array $options): array
{
// Default provider adds "approval_prompt=auto", but using both "prompt" and "approval_prompt" together is not allowed
$params = parent::getAuthorizationParameters($options);
diff --git a/src/Site/views/languageforge/container/languageforge.html.twig b/src/Site/views/languageforge/container/languageforge.html.twig
index 527e2116c6..422e5915ac 100644
--- a/src/Site/views/languageforge/container/languageforge.html.twig
+++ b/src/Site/views/languageforge/container/languageforge.html.twig
@@ -73,7 +73,7 @@
{% verbatim %}
{% endverbatim %}
@@ -81,7 +81,7 @@
+ ng-click="$ctrl.openShareWithOthersModal($event)">
Share
diff --git a/src/angular-app/bellows/apps/siteadmin/site-admin-users.component.ts b/src/angular-app/bellows/apps/siteadmin/site-admin-users.component.ts
index 2737f8654c..9bd10ef0dd 100644
--- a/src/angular-app/bellows/apps/siteadmin/site-admin-users.component.ts
+++ b/src/angular-app/bellows/apps/siteadmin/site-admin-users.component.ts
@@ -207,7 +207,7 @@ export class SiteAdminUsersController implements angular.IController {
return;
}
- this.userService.remove(userIds, result => {
+ this.userService.deleteAccounts(userIds, result => {
if (result.ok) {
if (result.data === 1) {
this.notice.push(this.notice.SUCCESS, '1 user was deleted');
diff --git a/src/angular-app/bellows/apps/userprofile/user-profile-app.component.html b/src/angular-app/bellows/apps/userprofile/user-profile-app.component.html
index cc91b7cf6c..e9d446803d 100644
--- a/src/angular-app/bellows/apps/userprofile/user-profile-app.component.html
+++ b/src/angular-app/bellows/apps/userprofile/user-profile-app.component.html
@@ -2,7 +2,7 @@
+
+
+
+
+
+
+ Investigating the feasibility of account deletion...
+
+
+
+
+
+
+
Deleting your account is a permanent action and cannot be undone. Your account will be deleted, and you will be removed from all projects you are a member of. You will no longer be able to log in.
+
+
+
You are a project owner.
+
+ First, transfer your projects to a new owner or delete them:
+
+
+
+
+
+
+
+
+
+
@@ -143,13 +204,13 @@
Tell us about yourself...
+
-
diff --git a/src/angular-app/bellows/apps/userprofile/user-profile-app.component.ts b/src/angular-app/bellows/apps/userprofile/user-profile-app.component.ts
index c451eda3ee..3b7bc796e6 100644
--- a/src/angular-app/bellows/apps/userprofile/user-profile-app.component.ts
+++ b/src/angular-app/bellows/apps/userprofile/user-profile-app.component.ts
@@ -1,6 +1,7 @@
import * as angular from 'angular';
import {UserService} from '../../core/api/user.service';
+import { ProjectService } from '../../core/api/project.service';
import {ApplicationHeaderService} from '../../core/application-header.service';
import {BreadcrumbService} from '../../core/breadcrumbs/breadcrumb.service';
import {SiteWideNoticeService} from '../../core/site-wide-notice-service';
@@ -8,6 +9,7 @@ import {ModalService} from '../../core/modal/modal.service';
import {NoticeService} from '../../core/notice/notice.service';
import {UtilityService} from '../../core/utility.service';
import {UserProfile} from '../../shared/model/user-profile.model';
+import { Project } from '../../shared/model/project.model';
interface UserProfileAppControllerScope extends angular.IScope {
userprofileForm: angular.IFormController;
@@ -29,21 +31,28 @@ export class UserProfileAppController implements angular.IController {
emailExists: boolean;
usernameExists: boolean;
+ ownsAProject: boolean = false;
+
+ projects: Project[] = [];
+ finishedLoadingOwnedProjects: boolean = false;
+ ownedProjects: Project[] = [];
+
private initColor = '';
private initShape = '';
static $inject = ['$scope', '$window',
- 'userService', 'modalService', 'silNoticeService',
+ 'userService', 'projectService', 'modalService', 'silNoticeService',
'breadcrumbService',
'siteWideNoticeService',
'applicationHeaderService'];
constructor(private $scope: UserProfileAppControllerScope, private $window: angular.IWindowService,
- private userService: UserService, private modalService: ModalService, private notice: NoticeService,
+ private userService: UserService, private projectService: ProjectService, private modalService: ModalService, private notice: NoticeService,
private breadcrumbService: BreadcrumbService,
private siteWideNoticeService: SiteWideNoticeService,
private applicationHeaderService: ApplicationHeaderService) {}
$onInit(): void {
+ this.finishedLoadingOwnedProjects = false;
this.user.avatar_ref = UserProfileAppController.getAvatarRef('', '');
this.$scope.$watch(() => this.user.avatar_color, () => {
@@ -62,7 +71,8 @@ export class UserProfileAppController implements angular.IController {
this.siteWideNoticeService.displayNotices();
- this.loadUser(); // load the user data right away
+ this.loadUser() // load the user data right away
+ .then(() => this.queryProjectsForUser());
this.dropdown.avatarColors = [
{ value: 'purple4', label: 'Purple' },
@@ -101,6 +111,7 @@ export class UserProfileAppController implements angular.IController {
{ value: 'sheep', label: 'Sheep' },
{ value: 'tortoise', label: 'Tortoise' }
];
+
}
validateForm(): void {
@@ -160,13 +171,13 @@ export class UserProfileAppController implements angular.IController {
// catch is necessary to properly implement promise API, which angular 1.6 complains if we
// don't have a catch
}).catch(() => {});
- } else {
+ } else if (this.user.active) {
this.updateUser();
}
}
- private loadUser(): void {
- this.userService.readProfile(result => {
+ private loadUser(): angular.IPromise {
+ return this.userService.readProfile(result => {
if (result.ok) {
this.user = result.data.userProfile;
this.originalUsername = this.user.username;
@@ -203,6 +214,36 @@ export class UserProfileAppController implements angular.IController {
});
}
+ queryProjectsForUser() {
+ this.finishedLoadingOwnedProjects = false;
+ this.projects = [];
+ this.ownedProjects = [];
+ this.projectService.list().then((projects: Project[]) => {
+ this.projects = projects || [];
+ this.ownedProjects = projects.filter(proj => proj.ownerId === this.user.id);
+ this.ownsAProject = this.ownedProjects.length > 0;
+ this.finishedLoadingOwnedProjects = true;
+ }).catch(console.error);
+ }
+
+ deleteOwnAccount() {
+ const modalOptions = {
+ closeButtonText: 'Cancel',
+ actionButtonText: 'Delete',
+ headerText: 'Permanently delete your account?',
+ bodyText: 'Are you sure you want to delete your account?\n' +
+ 'This is a permanent action and cannot be restored.'
+ };
+ this.modalService.showModal({}, modalOptions).then(() => {
+ this.userService.deleteAccounts([this.user.id]).then(() => {
+ this.notice.push(this.notice.SUCCESS, 'Your account was permanently deleted');
+ this.$window.location.href = '/auth/logout'; // goes to the log in screen
+ });
+ }, () => {});
+
+ }
+
+
static getAvatarUrl(avatarRef: string): string {
if (avatarRef) {
return (avatarRef.startsWith('http')) ? avatarRef : '/Site/views/shared/image/avatar/' + avatarRef;
diff --git a/src/angular-app/bellows/core/api/json-rpc.service.ts b/src/angular-app/bellows/core/api/json-rpc.service.ts
index 717a62b713..c762c08ae0 100644
--- a/src/angular-app/bellows/core/api/json-rpc.service.ts
+++ b/src/angular-app/bellows/core/api/json-rpc.service.ts
@@ -100,11 +100,7 @@ export class JsonRpcService {
type = 'You don\'t have sufficient privileges.';
break;
default:
- // silently swallow unknown exceptions and don't bug (heh) the user about things they can't fix
- // this.error.notify('Exception',
- // 'An exception occurred in the application, but\nthe developers have already been notified.',
- // response.data.error.message);
- return;
+ type = 'Exception';
}
this.error.error(type, response.data.error.message);
diff --git a/src/angular-app/bellows/core/api/user.service.ts b/src/angular-app/bellows/core/api/user.service.ts
index 9e04d95e53..f67dfe1b95 100644
--- a/src/angular-app/bellows/core/api/user.service.ts
+++ b/src/angular-app/bellows/core/api/user.service.ts
@@ -25,7 +25,7 @@ export class UserService {
return this.api.call('user_updateProfile', [params], callback);
}
- remove(userIds: string[], callback?: JsonRpcCallback) {
+ deleteAccounts(userIds: string[], callback?: JsonRpcCallback) {
return this.api.call('user_delete', [userIds], callback);
}
diff --git a/src/angular-app/bellows/core/navbar.controller.ts b/src/angular-app/bellows/core/navbar.controller.ts
index 71c365f4a4..4ef0f61760 100644
--- a/src/angular-app/bellows/core/navbar.controller.ts
+++ b/src/angular-app/bellows/core/navbar.controller.ts
@@ -90,13 +90,14 @@ export class NavbarController implements angular.IController {
}
}
- openShareWithOthersModal(): void {
+ openShareWithOthersModal(event: UIEvent): void {
const modalInstance = this.$modal.open({
component: 'shareWithOthersModal'
});
modalInstance.result.then(data => {
// TODO: save the data if not already
}, () => {});
+ event.preventDefault();
}
private isNotInProject(): boolean {
diff --git a/src/angular-app/languageforge/lexicon/core/lexicon-project.service.ts b/src/angular-app/languageforge/lexicon/core/lexicon-project.service.ts
index 216bb06c36..42245b11ae 100644
--- a/src/angular-app/languageforge/lexicon/core/lexicon-project.service.ts
+++ b/src/angular-app/languageforge/lexicon/core/lexicon-project.service.ts
@@ -54,7 +54,7 @@ export class LexiconProjectService {
settings.push(new HeaderSetting(
'userManagementLink',
'Share With Others',
- '',
+ '#',
false
));
settings.push(new HeaderSetting(
diff --git a/src/angular-app/languageforge/lexicon/editor/field/dc-audio.component.ts b/src/angular-app/languageforge/lexicon/editor/field/dc-audio.component.ts
index 1d8228d772..2990f2f1d0 100644
--- a/src/angular-app/languageforge/lexicon/editor/field/dc-audio.component.ts
+++ b/src/angular-app/languageforge/lexicon/editor/field/dc-audio.component.ts
@@ -115,8 +115,9 @@ export class FieldAudioController implements angular.IController {
this.dcFilename = response.data.data.fileName;
this.showAudioUpload = false;
this.notice.push(this.notice.SUCCESS, 'File uploaded successfully.');
- if(response.data.data.fileSize > 1000000){ //1 MB file size limit 2022-10
- this.notice.push(this.notice.WARN, 'WARNING: Because the audio file - ' + response.data.data.fileName + ' - is larger than 1 MB, it will not be synced with FLEx.');
+ if (response.data.data.fileSize > 10000000) {
+ // `MaximumFileSize` in https://github.com/sillsdev/chorus/blob/master/src/LibChorus/FileTypeHandlers/audio/AudioFileTypeHandler.cs
+ this.notice.push(this.notice.WARN, 'WARNING: Because the audio file - ' + response.data.data.fileName + ' - is larger than 10 MB, it will not be synced with FLEx.');
}
} else {
this.notice.push(this.notice.ERROR, response.data.data.errorMessage);
diff --git a/src/angular-app/languageforge/lexicon/editor/field/dc-picture.component.ts b/src/angular-app/languageforge/lexicon/editor/field/dc-picture.component.ts
index 6a61936c75..df9bf2b7d1 100644
--- a/src/angular-app/languageforge/lexicon/editor/field/dc-picture.component.ts
+++ b/src/angular-app/languageforge/lexicon/editor/field/dc-picture.component.ts
@@ -113,8 +113,9 @@ export class FieldPictureController implements angular.IController {
if (isUploadSuccess) {
this.upload.progress = 100.0;
this.addPicture(response.data.data.fileName);
- if(response.data.data.fileSize > 1000000){ //1 MB file size limit 2022-10
- this.notice.push(this.notice.WARN, 'WARNING: Because the image file - ' + response.data.data.fileName + ' - is larger than 1 MB, it will not be synced with FLEx.');
+ if (response.data.data.fileSize > 10000000) {
+ // `MaximumFileSize` in https://github.com/sillsdev/chorus/blob/master/src/LibChorus/FileTypeHandlers/image/ImageFileTypeHandler.cs
+ this.notice.push(this.notice.WARN, 'WARNING: Because the image file - ' + response.data.data.fileName + ' - is larger than 10 MB, it will not be synced with FLEx.');
}
this.upload.showAddPicture = false;
} else {
diff --git a/src/composer.json b/src/composer.json
index 572985a70c..709c3a98f5 100644
--- a/src/composer.json
+++ b/src/composer.json
@@ -13,14 +13,14 @@
"guzzlehttp/guzzle": "^7",
"litipk/php-jiffy": "^1",
"league/oauth2-client": "^2",
- "league/oauth2-google": "^2",
+ "league/oauth2-google": "^4",
"league/oauth2-facebook": "^2",
"mongodb/mongodb": "^1",
"ocramius/lazy-property": "dev-feature/factory",
"ramsey/uuid": "^4.2",
"silex/silex": "~1.3.2",
"sillsdev/web-php-utilities": "dev-master",
- "swiftmailer/swiftmailer": "^5",
+ "swiftmailer/swiftmailer": "^6",
"symfony/security": "^2.7",
"symfony/twig-bridge": "^2.7",
"twig/twig": "^2",
diff --git a/src/composer.lock b/src/composer.lock
index 5f91ee96d1..2007e26993 100644
--- a/src/composer.lock
+++ b/src/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "d2449b529538a4390358d652937682cb",
+ "content-hash": "dd7f053295cdfdcc5b121a0078cb0f0f",
"packages": [
{
"name": "brick/math",
@@ -64,6 +64,176 @@
],
"time": "2021-08-15T20:50:18+00:00"
},
+ {
+ "name": "doctrine/deprecations",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/deprecations.git",
+ "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
+ "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1|^8.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^9",
+ "phpunit/phpunit": "^7.5|^8.5|^9.5",
+ "psr/log": "^1|^2|^3"
+ },
+ "suggest": {
+ "psr/log": "Allows logging deprecations via PSR-3 logger implementation"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": ["MIT"],
+ "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
+ "homepage": "https://www.doctrine-project.org/",
+ "support": {
+ "issues": "https://github.com/doctrine/deprecations/issues",
+ "source": "https://github.com/doctrine/deprecations/tree/v1.0.0"
+ },
+ "time": "2022-05-02T15:47:09+00:00"
+ },
+ {
+ "name": "doctrine/lexer",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/lexer.git",
+ "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124",
+ "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/deprecations": "^1.0",
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^9 || ^10",
+ "phpstan/phpstan": "^1.3",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+ "psalm/plugin-phpunit": "^0.18.3",
+ "vimeo/psalm": "^4.11 || ^5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Common\\Lexer\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": ["MIT"],
+ "authors": [
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
+ "homepage": "https://www.doctrine-project.org/projects/lexer.html",
+ "keywords": ["annotations", "docblock", "lexer", "parser", "php"],
+ "support": {
+ "issues": "https://github.com/doctrine/lexer/issues",
+ "source": "https://github.com/doctrine/lexer/tree/2.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-12-14T08:49:07+00:00"
+ },
+ {
+ "name": "egulias/email-validator",
+ "version": "3.2.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/egulias/EmailValidator.git",
+ "reference": "b531a2311709443320c786feb4519cfaf94af796"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b531a2311709443320c786feb4519cfaf94af796",
+ "reference": "b531a2311709443320c786feb4519cfaf94af796",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/lexer": "^1.2|^2",
+ "php": ">=7.2",
+ "symfony/polyfill-intl-idn": "^1.15"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.8|^9.3.3",
+ "vimeo/psalm": "^4"
+ },
+ "suggest": {
+ "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Egulias\\EmailValidator\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": ["MIT"],
+ "authors": [
+ {
+ "name": "Eduardo Gulias Davis"
+ }
+ ],
+ "description": "A library for validating emails against several RFCs",
+ "homepage": "https://github.com/egulias/EmailValidator",
+ "keywords": ["email", "emailvalidation", "emailvalidator", "validation", "validator"],
+ "support": {
+ "issues": "https://github.com/egulias/EmailValidator/issues",
+ "source": "https://github.com/egulias/EmailValidator/tree/3.2.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/egulias",
+ "type": "github"
+ }
+ ],
+ "time": "2023-01-02T17:26:14+00:00"
+ },
{
"name": "firebase/php-jwt",
"version": "v6.3.2",
@@ -581,26 +751,26 @@
},
{
"name": "league/oauth2-google",
- "version": "2.2.0",
+ "version": "4.0.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/oauth2-google.git",
- "reference": "c0faed29ec6d665ce3234e01f62029516cee4c02"
+ "reference": "db6d8ad67cdd7d014a1e5dd5c204a319a966de86"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/oauth2-google/zipball/c0faed29ec6d665ce3234e01f62029516cee4c02",
- "reference": "c0faed29ec6d665ce3234e01f62029516cee4c02",
+ "url": "https://api.github.com/repos/thephpleague/oauth2-google/zipball/db6d8ad67cdd7d014a1e5dd5c204a319a966de86",
+ "reference": "db6d8ad67cdd7d014a1e5dd5c204a319a966de86",
"shasum": ""
},
"require": {
- "league/oauth2-client": "^2.0"
+ "league/oauth2-client": "^2.0",
+ "php": ">=7.3"
},
"require-dev": {
- "eloquent/phony": "^0.14.6",
- "phpunit/phpunit": "^5.7",
- "satooshi/php-coveralls": "^2.0",
- "squizlabs/php_codesniffer": "^2.0"
+ "eloquent/phony-phpunit": "^6.0 || ^7.1",
+ "phpunit/phpunit": "^8.0 || ^9.0",
+ "squizlabs/php_codesniffer": "^3.0"
},
"type": "library",
"autoload": {
@@ -621,9 +791,9 @@
"keywords": ["Authentication", "authorization", "client", "google", "oauth", "oauth2"],
"support": {
"issues": "https://github.com/thephpleague/oauth2-google/issues",
- "source": "https://github.com/thephpleague/oauth2-google/tree/master"
+ "source": "https://github.com/thephpleague/oauth2-google/tree/4.0.0"
},
- "time": "2018-03-19T17:28:55+00:00"
+ "time": "2021-03-04T21:12:06+00:00"
},
{
"name": "litipk/php-jiffy",
@@ -1404,29 +1574,36 @@
},
{
"name": "swiftmailer/swiftmailer",
- "version": "v5.4.12",
+ "version": "v6.3.0",
"source": {
"type": "git",
"url": "https://github.com/swiftmailer/swiftmailer.git",
- "reference": "181b89f18a90f8925ef805f950d47a7190e9b950"
+ "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/181b89f18a90f8925ef805f950d47a7190e9b950",
- "reference": "181b89f18a90f8925ef805f950d47a7190e9b950",
+ "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8a5d5072dca8f48460fce2f4131fcc495eec654c",
+ "reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "egulias/email-validator": "^2.0|^3.1",
+ "php": ">=7.0.0",
+ "symfony/polyfill-iconv": "^1.0",
+ "symfony/polyfill-intl-idn": "^1.10",
+ "symfony/polyfill-mbstring": "^1.0"
},
"require-dev": {
- "mockery/mockery": "~0.9.1",
- "symfony/phpunit-bridge": "~3.2"
+ "mockery/mockery": "^1.0",
+ "symfony/phpunit-bridge": "^4.4|^5.4"
+ },
+ "suggest": {
+ "ext-intl": "Needed to support internationalized email addresses"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "5.4-dev"
+ "dev-master": "6.2-dev"
}
},
"autoload": {
@@ -1448,10 +1625,20 @@
"keywords": ["email", "mail", "mailer"],
"support": {
"issues": "https://github.com/swiftmailer/swiftmailer/issues",
- "source": "https://github.com/swiftmailer/swiftmailer/tree/v5.4.12"
+ "source": "https://github.com/swiftmailer/swiftmailer/tree/v6.3.0"
},
+ "funding": [
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/swiftmailer/swiftmailer",
+ "type": "tidelift"
+ }
+ ],
"abandoned": "symfony/mailer",
- "time": "2018-07-31T09:26:32+00:00"
+ "time": "2021-10-18T15:26:12+00:00"
},
{
"name": "symfony/debug",
@@ -1848,6 +2035,226 @@
],
"time": "2022-11-03T14:55:06+00:00"
},
+ {
+ "name": "symfony/polyfill-iconv",
+ "version": "v1.27.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-iconv.git",
+ "reference": "927013f3aac555983a5059aada98e1907d842695"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/927013f3aac555983a5059aada98e1907d842695",
+ "reference": "927013f3aac555983a5059aada98e1907d842695",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-iconv": "*"
+ },
+ "suggest": {
+ "ext-iconv": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.27-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": ["bootstrap.php"],
+ "psr-4": {
+ "Symfony\\Polyfill\\Iconv\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": ["MIT"],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Iconv extension",
+ "homepage": "https://symfony.com",
+ "keywords": ["compatibility", "iconv", "polyfill", "portable", "shim"],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-iconv/tree/v1.27.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-11-03T14:55:06+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-idn",
+ "version": "v1.27.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-idn.git",
+ "reference": "639084e360537a19f9ee352433b84ce831f3d2da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da",
+ "reference": "639084e360537a19f9ee352433b84ce831f3d2da",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1",
+ "symfony/polyfill-intl-normalizer": "^1.10",
+ "symfony/polyfill-php72": "^1.10"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.27-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": ["bootstrap.php"],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Idn\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": ["MIT"],
+ "authors": [
+ {
+ "name": "Laurent Bassin",
+ "email": "laurent@bassin.info"
+ },
+ {
+ "name": "Trevor Rowbotham",
+ "email": "trevor.rowbotham@pm.me"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
+ "homepage": "https://symfony.com",
+ "keywords": ["compatibility", "idn", "intl", "polyfill", "portable", "shim"],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-11-03T14:55:06+00:00"
+ },
+ {
+ "name": "symfony/polyfill-intl-normalizer",
+ "version": "v1.27.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
+ "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6",
+ "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-intl": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.27-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": ["bootstrap.php"],
+ "psr-4": {
+ "Symfony\\Polyfill\\Intl\\Normalizer\\": ""
+ },
+ "classmap": ["Resources/stubs"]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": ["MIT"],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for intl's Normalizer class and related functions",
+ "homepage": "https://symfony.com",
+ "keywords": ["compatibility", "intl", "normalizer", "polyfill", "portable", "shim"],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-11-03T14:55:06+00:00"
+ },
{
"name": "symfony/polyfill-mbstring",
"version": "v1.27.0",
@@ -2988,16 +3395,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v4.15.2",
+ "version": "v4.15.3",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc"
+ "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
- "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039",
+ "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039",
"shasum": ""
},
"require": {
@@ -3031,9 +3438,9 @@
"keywords": ["parser", "php"],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3"
},
- "time": "2022-11-12T15:38:23+00:00"
+ "time": "2023-01-16T22:05:37+00:00"
},
{
"name": "phar-io/manifest",
@@ -3425,20 +3832,20 @@
},
{
"name": "phpunit/phpunit",
- "version": "9.5.27",
+ "version": "9.5.28",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38"
+ "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2bc7ffdca99f92d959b3f2270529334030bba38",
- "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/954ca3113a03bf780d22f07bf055d883ee04b65e",
+ "reference": "954ca3113a03bf780d22f07bf055d883ee04b65e",
"shasum": ""
},
"require": {
- "doctrine/instantiator": "^1.3.1",
+ "doctrine/instantiator": "^1.3.1 || ^2",
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
@@ -3495,7 +3902,7 @@
"keywords": ["phpunit", "testing", "xunit"],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.27"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.28"
},
"funding": [
{
@@ -3511,7 +3918,7 @@
"type": "tidelift"
}
],
- "time": "2022-12-09T07:31:23+00:00"
+ "time": "2023-01-14T12:32:24+00:00"
},
{
"name": "sebastian/cli-parser",
diff --git a/test/e2e/constants.ts b/test/e2e/constants.ts
index 1551a7001c..92cfd7497a 100644
--- a/test/e2e/constants.ts
+++ b/test/e2e/constants.ts
@@ -125,3 +125,7 @@ export const files = [
] as const;
export const appUrl = 'http://localhost:3238/';
+
+export const SEC = 1000;
+export const MIN = SEC * 60;
+export const YEAR = 365 * 24 * 60 * MIN;
diff --git a/test/e2e/global-setup.ts b/test/e2e/global-setup.ts
index 7b985ab0b9..0729cbb793 100644
--- a/test/e2e/global-setup.ts
+++ b/test/e2e/global-setup.ts
@@ -1,16 +1,14 @@
import { BrowserType, chromium, firefox, FullConfig, Page, webkit } from '@playwright/test';
import * as fs from 'fs';
-import { appUrl, users } from './constants';
+import { appUrl, users, YEAR } from './constants';
import { getStorageStatePath, login, UserDetails, UserTestService } from './utils';
-const SESSION_LIFETIME = 365 * 24 * 60 * 60 * 1000; // 1 year, in milliseconds
-
async function initE2EUser(page: Page, user: UserDetails) {
const context = page.context();
await new UserTestService(context.request).createUser(user);
// Now log in and ensure there's a storage state saved
- const sessionCutoff = Date.now() - SESSION_LIFETIME;
+ const sessionCutoff = Date.now() - YEAR;
const path = getStorageStatePath(user);
if (fs.existsSync(path) && fs.statSync(path)?.ctimeMs >= sessionCutoff) {
// Storage state file is recent, no need to re-create it
diff --git a/test/e2e/pages/base-page.ts b/test/e2e/pages/base-page.ts
index 41c1ac64bd..2fda1ef11d 100644
--- a/test/e2e/pages/base-page.ts
+++ b/test/e2e/pages/base-page.ts
@@ -1,8 +1,8 @@
import { Locator, Page, APIRequestContext } from '@playwright/test';
+import { appUrl } from '../constants';
import { NoticeElement, PageHeader } from "./components";
export interface GotoOptions {
- waitFor?: Locator;
expectRedirect?: boolean;
}
@@ -21,10 +21,13 @@ export abstract class BasePage
}
readonly request: APIRequestContext;
-
readonly header = new PageHeader(this.page);
readonly noticeList = new NoticeElement(this.page);
+ protected get isCurrentPage(): boolean {
+ return this.page.url().startsWith(appUrl) && !!this.page.url().match(this.urlPattern);
+ }
+
private readonly locators: Locator[];
private readonly urlPattern = this.url.includes('#') ?
new RegExp(`${this.url}`) : new RegExp(`${this.url}/?(#|$)`);
@@ -39,14 +42,16 @@ export abstract class BasePage
* @returns the page for convenience/chaining
*/
async goto(options?: GotoOptions): Promise {
- await Promise.all([
- this.page.goto(this.url),
- options?.expectRedirect || this.waitFor(), // this page won't load if a redirect happens
- options?.waitFor?.waitFor(),
- ]).catch(error => {
- console.error(error);
- throw error;
- });
+ if (this.isCurrentPage) {
+ return this;
+ }
+
+ await this.page.goto(this.url)
+ .then(_ => options?.expectRedirect ? undefined : this.waitFor()) // this page won't load if a redirect happens)
+ .catch(error => {
+ console.error(error);
+ throw error;
+ });
return this;
}
@@ -56,7 +61,7 @@ export abstract class BasePage
*/
async waitFor(): Promise {
await Promise.all([
- this.page.url().match(this.urlPattern) ?? this.page.waitForURL(this.urlPattern),
+ this.isCurrentPage || this.page.waitForURL(this.urlPattern),
...this.locators.map(wait => wait.waitFor()),
]);
return this;
diff --git a/test/e2e/pages/configuration-input-systems.tab.ts b/test/e2e/pages/configuration-input-systems.tab.ts
index 848909c7f7..6d478be9c4 100644
--- a/test/e2e/pages/configuration-input-systems.tab.ts
+++ b/test/e2e/pages/configuration-input-systems.tab.ts
@@ -20,18 +20,6 @@ export class ConfigurationPageInputSystemsTab extends ConfigurationPage {
super(page, project, page.locator(inputSystemListSelector));
}
- async goto(options?: GotoOptions): Promise {
- await Promise.all([
- this.page.goto(this.url),
- options?.waitFor?.waitFor(),
- ]);
- await Promise.all([
- this.tabLinks.inputSystems.click(),
- this.waitFor(),
- ]);
- return this;
- }
-
inputSystemOption(inputSystem: string): Locator {
return this.inputSystemList.locator(`div:has-text("${inputSystem}")`);
}
diff --git a/test/e2e/pages/editor.page.ts b/test/e2e/pages/editor.page.ts
index ec780eaf6b..7f3d80b692 100644
--- a/test/e2e/pages/editor.page.ts
+++ b/test/e2e/pages/editor.page.ts
@@ -84,6 +84,10 @@ export class EditorPage extends BasePage {
readonly comments = this.commentContainer.locator(`> div:visible`);
readonly comment = (n: number) => this.locator(`.commentListContainer > div:nth-child(${n}) .commentContainer`);
+ protected override get isCurrentPage(): boolean {
+ return this.page.url().endsWith(this.url) || this.page.url().includes('#!/editor');
+ }
+
private readonly lexAppToolbar = {
backToListButton: this.locator('#toListLink'),
toggleCommentsButton: this.locator('#toCommentsLink'),
@@ -106,18 +110,13 @@ export class EditorPage extends BasePage {
}
async goto(options?: EditorGotoOptions): Promise {
- const alreadyOnPage = this.page.url().endsWith(this.url) || this.page.url().includes('#!/editor');
await super.goto(options);
- const entryId = options?.entryId;
- if (entryId || alreadyOnPage) {
- // If we're navigating from one entry to another, then goto doesn't cause angular to load the new entry
+ if (options?.entryId) {
+ // Navigating from one entry to another via URL/goto doesn't cause angular to load the new entry
// clicking is a more realistic test anyway
- await Promise.all([
- this.locator(`[id^=entryId_${entryId ?? ''}]`).first().click(),
- this.waitFor(),
- ]);
+ await this.locator(`[id^=entryId_${options.entryId}]`).click();
+ await this.waitFor();
}
- await expect(this.locator('.page-name >> text=' + this.project.name)).toBeVisible();
return this;
}
diff --git a/test/e2e/pages/signup.page.ts b/test/e2e/pages/signup.page.ts
index 286cb76ce8..89d61cf1ce 100644
--- a/test/e2e/pages/signup.page.ts
+++ b/test/e2e/pages/signup.page.ts
@@ -62,7 +62,7 @@ export class SignupPage extends BasePage {
this.waitFor(),
]);
} else {
- await super.goto();
+ await super.goto(options);
}
return this;
}
diff --git a/test/e2e/pages/tabbed-page.ts b/test/e2e/pages/tabbed-page.ts
index 65fe4b50aa..1b7d3cf350 100644
--- a/test/e2e/pages/tabbed-page.ts
+++ b/test/e2e/pages/tabbed-page.ts
@@ -12,13 +12,10 @@ export abstract class TabbedBasePage extends BasePage {
protected abstract get tabLink(): Locator;
async goto(options?: GotoOptions): Promise {
- await Promise.all([
- this.page.goto(this.url),
- options?.waitFor?.waitFor(),
- ]);
+ await this.page.goto(this.url);
await Promise.all([
this.tabLink.click(),
- this.waitFor(),
+ options?.expectRedirect || this.waitFor(),
]);
return this;
}
diff --git a/test/e2e/pages/user-profile.page.ts b/test/e2e/pages/user-profile.page.ts
index 44b12f22fb..8d731c18d8 100644
--- a/test/e2e/pages/user-profile.page.ts
+++ b/test/e2e/pages/user-profile.page.ts
@@ -5,7 +5,8 @@ export class UserProfilePage extends BasePage {
readonly activitiesList = this.locator('[data-ng-repeat="item in filteredActivities"]');
readonly tabs = {
aboutMe: this.locator('#AboutMeTab'),
- myAccount: this.locator('#myAccountTab')
+ myAccount: this.locator('#myAccountTab'),
+ deleteAccount: this.locator('#DeleteTab')
};
readonly accountTab = {
@@ -21,13 +22,26 @@ export class UserProfilePage extends BasePage {
genderField: this.page.getByLabel('Gender'),
};
- private readonly saveBtn = this.locator('#saveBtn');
+ readonly deleteTab = {
+ confirmField: this.page.getByLabel('Confirm deletion by typing DELETE into the box below'),
+ }
+
+ private readonly saveBtn = this.locator('#saveMyAccountBtn:visible, #saveAboutMeBtn:visible');
+ private readonly deleteAccountButton = this.locator('#deleteAccountBtn')
- readonly modal = {
+
+ readonly changesModal = {
saveChangesBtn: this.locator('.modal-dialog button:has-text("Save changes")'),
};
- private readonly successNotice = this.noticeList.success('Profile updated successfully');
+ readonly deleteModal = {
+ confirmDeletionBtn: this.locator('.modal-dialog button:has-text("Delete")'),
+ };
+
+
+
+ private readonly successfulUpdateNotice = this.noticeList.success('Profile updated successfully');
+ private readonly successfulDeleteNotice = this.noticeList.success('Your account was permanently deleted');
constructor(page: Page) {
super(page, '/app/userprofile', page.locator('.page-name >> text=\'s User Profile'));
@@ -38,9 +52,21 @@ export class UserProfilePage extends BasePage {
await Promise.race([
// Some changes require confirmation and then log the user out (e.g. username)
- this.modal.saveChangesBtn.click(),
+ this.changesModal.saveChangesBtn.click(),
// others just show a success message
- this.successNotice.waitFor(),
+ this.successfulUpdateNotice.waitFor(),
]);
}
+
+ async deleteMyAccount(): Promise {
+ await this.deleteAccountButton.click();
+
+ await Promise.race([
+ // deletion confirmation
+ this.deleteModal.confirmDeletionBtn.click(),
+ // success message
+ this.successfulDeleteNotice.waitFor(),
+ ]);
+ }
+
}
diff --git a/test/e2e/playwright.config.ts b/test/e2e/playwright.config.ts
index e374459f6f..9ece04e7b0 100644
--- a/test/e2e/playwright.config.ts
+++ b/test/e2e/playwright.config.ts
@@ -1,6 +1,6 @@
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices, expect } from '@playwright/test';
-import { appUrl } from './constants';
+import { appUrl, MIN, SEC } from './constants';
import { matchers } from './utils/custom-matchers';
/**
@@ -20,13 +20,13 @@ const config: PlaywrightTestConfig = {
snapshotPathTemplate: '{testDir}/__expected-screenshots__/{testFilePath}/{arg}-{projectName}{ext}',
/* Maximum time one test can run for. For individual slower tests use test.slow(). */
- timeout: 30 * 1000,
+ timeout: 40 * SEC,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
- timeout: 5000,
+ timeout: 5 * SEC,
},
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
@@ -48,7 +48,7 @@ const config: PlaywrightTestConfig = {
reportSlowTests: {
max: 10,
- threshold: 30 * 1000,
+ threshold: 1 * MIN,
},
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
@@ -106,14 +106,14 @@ const config: PlaywrightTestConfig = {
if (!process.env._loggedAppStartup) { // Provide some feedback
process.env._loggedAppStartup = '1';
const timeout = (this as any).timeout;
- console.log(`Building and starting e2e-app if not already running (timeout: ${timeout / 60000}m)\n\n`);
+ console.log(`Building and starting e2e-app if not already running (timeout: ${timeout / MIN}m)\n\n`);
}
return 'make e2e-app && docker attach e2e-app';
},
cwd: '../..', // navigate to makefile
url: appUrl,
reuseExistingServer: true,
- timeout: 3 * 60000, // 3m
+ timeout: 3 * MIN,
},
};
diff --git a/test/e2e/tests/signup.spec.ts b/test/e2e/tests/signup.spec.ts
index 70912a60e2..c4b30b6829 100644
--- a/test/e2e/tests/signup.spec.ts
+++ b/test/e2e/tests/signup.spec.ts
@@ -4,131 +4,133 @@ import { test } from '../fixtures';
import { ProjectsPage, SignupPage } from '../pages';
test.describe('Signup', () => {
- let signupPage: SignupPage;
- const validPassword = 'languageforge';
- const unusedName = 'Super amazing unused name';
- const unusedEmail = 'unused-email@valuable-but-unnoticed.com';
-
- test.beforeEach(async ({ tab }) => {
- signupPage = new SignupPage(tab);
- })
-
- test('Cannot submit if email is invalid', async () => {
- await signupPage.goto();
-
- await signupPage.nameInput.fill(unusedName);
- await signupPage.emailInput.fill('email-without-at');
- await signupPage.passwordInput.fill(validPassword);
- await signupPage.captcha.setInvalidCaptcha();
- await expect(signupPage.emailInvalid).toBeVisible();
- await expect(signupPage.signupButton).toBeDisabled();
- });
-
- test('Cannot submit if password is weak', async () => {
- await signupPage.goto();
-
- await signupPage.nameInput.fill(unusedName);
- await signupPage.emailInput.fill(unusedEmail);
- await signupPage.passwordInput.fill(validPassword);
- await signupPage.captcha.setInvalidCaptcha();
- await expect(signupPage.signupButton).toBeEnabled();
- const passwordTooShort = '123456';
- await signupPage.passwordInput.fill(passwordTooShort);
- await signupPage.captcha.setInvalidCaptcha();
- await expect(signupPage.passwordIsWeak).toBeVisible();
- await expect(signupPage.signupButton).not.toBeEnabled();
- });
-
- test('Can submit if the password is strong', async () => {
- await signupPage.goto();
-
- await signupPage.nameInput.fill(unusedName);
- await signupPage.emailInput.fill(unusedEmail);
- await signupPage.passwordInput.fill(validPassword);
- await signupPage.captcha.setInvalidCaptcha();
- await expect(signupPage.passwordIsWeak).not.toBeVisible();
- await expect(signupPage.signupButton).toBeEnabled();
- });
-
- test('Can submit if password is showing and not weak', async () => {
- await signupPage.goto();
-
- await signupPage.nameInput.fill(unusedName);
- await signupPage.emailInput.fill(unusedEmail);
- await signupPage.passwordInput.fill(validPassword);
- await signupPage.showPassword.click();
- await signupPage.captcha.setInvalidCaptcha();
- await expect(signupPage.passwordIsWeak).not.toBeVisible();
- await expect(signupPage.signupButton).toBeEnabled();
- });
-
- test('Cannot submit if captcha not selected', async () => {
- await signupPage.goto();
-
- await signupPage.nameInput.fill(unusedName);
- await signupPage.emailInput.fill(unusedEmail);
- await signupPage.passwordInput.fill(validPassword);
- await expect(signupPage.signupButton).toBeDisabled();
- });
-
- test('Captcha is invalid but user can still submit form', async () => {
- await signupPage.goto();
-
- await signupPage.nameInput.fill(unusedName);
- await signupPage.emailInput.fill(unusedEmail);
- await signupPage.passwordInput.fill(validPassword);
- await signupPage.captcha.setInvalidCaptcha();
- await signupPage.signupButton.click();
- await expect(signupPage.captchaInvalid).toBeVisible();
- });
-
- test('Finds the admin user (case insensitive) already exists', async () => {
- await signupPage.goto();
-
- await signupPage.nameInput.fill(unusedName);
- await signupPage.emailInput.fill(users.admin.email.toUpperCase());
- await signupPage.passwordInput.fill(validPassword);
- await signupPage.captcha.setValidCaptcha();
- await expect(signupPage.signupButton).toBeEnabled();
- await signupPage.signupButton.click();
- await expect(signupPage.emailTaken).toBeVisible();
- });
-
- test('Can prefill email address that can\'t be changed', async () => {
- await signupPage.goto({ email: unusedEmail });
-
- await expect(signupPage.emailInput).toBeDisabled();
- });
-
- test('Can prefill email address that already exists', async () => {
- await signupPage.goto({ email: users.admin.email });
-
- await signupPage.nameInput.fill(unusedName);
- await signupPage.passwordInput.fill(validPassword);
- await signupPage.captcha.setValidCaptcha();
- await signupPage.signupButton.click();
- await expect(signupPage.emailTaken).toBeVisible();
- });
-
- test('Can signup a new user', async () => {
- await signupPage.goto();
-
- await signupPage.nameInput.fill(unusedName);
- await signupPage.emailInput.fill(unusedEmail);
- await signupPage.passwordInput.fill(validPassword);
- await signupPage.captcha.setValidCaptcha();
- await expect(signupPage.signupButton).toBeEnabled();
- await signupPage.signupButton.click();
- // Verify new user logged in and redirected to projects page
- await new ProjectsPage(signupPage.page).waitFor();
+ test.describe('New user', () => {
+ let signupPage: SignupPage;
+ const validPassword = 'languageforge';
+ const unusedName = 'Super amazing unused name';
+ const unusedEmail = 'unused-email@valuable-but-unnoticed.com';
+
+ test.beforeEach(async ({ tab }) => {
+ signupPage = new SignupPage(tab);
+ })
+
+ test('Cannot submit if email is invalid', async () => {
+ await signupPage.goto();
+
+ await signupPage.nameInput.fill(unusedName);
+ await signupPage.emailInput.fill('email-without-at');
+ await signupPage.passwordInput.fill(validPassword);
+ await signupPage.captcha.setInvalidCaptcha();
+ await expect(signupPage.emailInvalid).toBeVisible();
+ await expect(signupPage.signupButton).toBeDisabled();
+ });
+
+ test('Cannot submit if password is weak', async () => {
+ await signupPage.goto();
+
+ await signupPage.nameInput.fill(unusedName);
+ await signupPage.emailInput.fill(unusedEmail);
+ await signupPage.passwordInput.fill(validPassword);
+ await signupPage.captcha.setInvalidCaptcha();
+ await expect(signupPage.signupButton).toBeEnabled();
+ const passwordTooShort = '123456';
+ await signupPage.passwordInput.fill(passwordTooShort);
+ await signupPage.captcha.setInvalidCaptcha();
+ await expect(signupPage.passwordIsWeak).toBeVisible();
+ await expect(signupPage.signupButton).not.toBeEnabled();
+ });
+
+ test('Can submit if the password is strong', async () => {
+ await signupPage.goto();
+
+ await signupPage.nameInput.fill(unusedName);
+ await signupPage.emailInput.fill(unusedEmail);
+ await signupPage.passwordInput.fill(validPassword);
+ await signupPage.captcha.setInvalidCaptcha();
+ await expect(signupPage.passwordIsWeak).not.toBeVisible();
+ await expect(signupPage.signupButton).toBeEnabled();
+ });
+
+ test('Can submit if password is showing and not weak', async () => {
+ await signupPage.goto();
+
+ await signupPage.nameInput.fill(unusedName);
+ await signupPage.emailInput.fill(unusedEmail);
+ await signupPage.passwordInput.fill(validPassword);
+ await signupPage.showPassword.click();
+ await signupPage.captcha.setInvalidCaptcha();
+ await expect(signupPage.passwordIsWeak).not.toBeVisible();
+ await expect(signupPage.signupButton).toBeEnabled();
+ });
+
+ test('Cannot submit if captcha not selected', async () => {
+ await signupPage.goto();
+
+ await signupPage.nameInput.fill(unusedName);
+ await signupPage.emailInput.fill(unusedEmail);
+ await signupPage.passwordInput.fill(validPassword);
+ await expect(signupPage.signupButton).toBeDisabled();
+ });
+
+ test('Captcha is invalid but user can still submit form', async () => {
+ await signupPage.goto();
+
+ await signupPage.nameInput.fill(unusedName);
+ await signupPage.emailInput.fill(unusedEmail);
+ await signupPage.passwordInput.fill(validPassword);
+ await signupPage.captcha.setInvalidCaptcha();
+ await signupPage.signupButton.click();
+ await expect(signupPage.captchaInvalid).toBeVisible();
+ });
+
+ test('Finds the admin user (case insensitive) already exists', async () => {
+ await signupPage.goto();
+
+ await signupPage.nameInput.fill(unusedName);
+ await signupPage.emailInput.fill(users.admin.email.toUpperCase());
+ await signupPage.passwordInput.fill(validPassword);
+ await signupPage.captcha.setValidCaptcha();
+ await expect(signupPage.signupButton).toBeEnabled();
+ await signupPage.signupButton.click();
+ await expect(signupPage.emailTaken).toBeVisible();
+ });
+
+ test('Can prefill email address that can\'t be changed', async () => {
+ await signupPage.goto({ email: unusedEmail });
+
+ await expect(signupPage.emailInput).toBeDisabled();
+ });
+
+ test('Can prefill email address that already exists', async () => {
+ await signupPage.goto({ email: users.admin.email });
+
+ await signupPage.nameInput.fill(unusedName);
+ await signupPage.passwordInput.fill(validPassword);
+ await signupPage.captcha.setValidCaptcha();
+ await signupPage.signupButton.click();
+ await expect(signupPage.emailTaken).toBeVisible();
+ });
+
+ test('Can signup a new user', async () => {
+ await signupPage.goto();
+
+ await signupPage.nameInput.fill(unusedName);
+ await signupPage.emailInput.fill(unusedEmail);
+ await signupPage.passwordInput.fill(validPassword);
+ await signupPage.captcha.setValidCaptcha();
+ await expect(signupPage.signupButton).toBeEnabled();
+ await signupPage.signupButton.click();
+
+ // Verify new user logged in and redirected to projects page
+ await new ProjectsPage(signupPage.page).waitFor();
+ });
});
- test('Redirects to projects page if already logged in', async ({ memberTab }) => {
- const signupPageMember = new SignupPage(memberTab);
- await Promise.all([
- new ProjectsPage(memberTab).waitFor(),
- signupPageMember.goto(),
- ]);
+ test.describe('Existing user', () => {
+ test('Redirects to projects page if already logged in', async ({ memberTab }) => {
+ await new SignupPage(memberTab).goto({ expectRedirect: true });
+ await new ProjectsPage(memberTab).waitFor();
+ });
});
});
diff --git a/test/e2e/tests/user-profile.spec.ts b/test/e2e/tests/user-profile.spec.ts
index 391dc8decb..5b5d614dab 100644
--- a/test/e2e/tests/user-profile.spec.ts
+++ b/test/e2e/tests/user-profile.spec.ts
@@ -99,4 +99,24 @@ test.describe('User Profile', () => {
await expect(userProfilePage.aboutMeTab.genderField).toHaveSelectedOption({ label: 'Female' });
});
+ test('Delete user account', async ({ tab, userService }) => {
+ const user = await userService.createRandomUser();
+ await login(tab, user);
+
+ const userProfilePage = new UserProfilePage(tab);
+ await userProfilePage.goto();
+ await userProfilePage.tabs.deleteAccount.click();
+
+ const confirmation = 'DELETE';
+ await userProfilePage.deleteTab.confirmField.fill(confirmation);
+
+ // User clicks 'Delete My Account' and is redirected to the login page
+ const [loginPage] = await Promise.all([
+ LoginPage.waitFor(tab),
+ userProfilePage.deleteMyAccount(),
+ ]);
+
+ });
+
+
});
diff --git a/test/php/model/shared/UserModelTest.php b/test/php/model/shared/UserModelTest.php
index 1c42077cb0..fb9c570cc1 100644
--- a/test/php/model/shared/UserModelTest.php
+++ b/test/php/model/shared/UserModelTest.php
@@ -337,28 +337,6 @@ public function testUserSetProperties_AdminAccessible_AllPropertiesSet()
$this->assertEquals($params["interfaceLanguageCode"], $user->interfaceLanguageCode);
}
- public function testUserRemove_UserMemberOfProject_ProjectLinkRemovedAsWell()
- {
- $environ = new MongoTestEnvironment();
- $environ->clean();
- $userId = $environ->createUser("user1", "user1", "user1@example.com");
- $user = new UserModel($userId);
- $project = $environ->createProject("testProject", "testProjectCode");
- $project->addUser($userId, ProjectRoles::CONTRIBUTOR);
- $project->write();
- $user->addProject($project->id->asString());
- $user->write();
-
- // delete the user
- $user->remove();
-
- // re-read the project
- $projectId = $project->id->asString();
- $project = new \Api\Model\Shared\ProjectModel($projectId);
-
- $this->assertFalse($project->userIsMember($userId));
- }
-
public function testHasForgottenPassword_KeyNotSetNoKeyConsume_HasNotForgotten()
{
$environ = new MongoTestEnvironment();
diff --git a/test/php/model/shared/commands/UserCommandsTest.php b/test/php/model/shared/commands/UserCommandsTest.php
index 46ec4aad23..c1daa591ae 100644
--- a/test/php/model/shared/commands/UserCommandsTest.php
+++ b/test/php/model/shared/commands/UserCommandsTest.php
@@ -49,19 +49,20 @@ protected function setUp(): void
self::$environ->clean();
}
- public function testDeleteUsers_1User_1Deleted()
+ public function testDeleteOwnAccount()
{
+ //User deletes own account
$userId = self::$environ->createUser("somename", "Some Name", "somename@example.com");
- $count = UserCommands::deleteUsers([$userId]);
+ $count = UserCommands::deleteAccounts([$userId], $userId);
$this->assertEquals(1, $count);
}
- public function testDeleteUsers_NoId_Exception()
+ public function testDeleteUsers_NoIds_Exception()
{
$this->expectException(Exception::class);
ini_set("display_errors", "0"); // do not show xdebug stack traces in PHPUnit output
- UserCommands::deleteUsers(null);
+ UserCommands::deleteAccounts(null, null);
ini_set("display_errors", "1"); // do not show xdebug stack traces in PHPUnit output
}
diff --git a/test/php/model/shared/dto/ActivityListDtoTest.php b/test/php/model/shared/dto/ActivityListDtoTest.php
index 141f55027f..ff55296d0b 100644
--- a/test/php/model/shared/dto/ActivityListDtoTest.php
+++ b/test/php/model/shared/dto/ActivityListDtoTest.php
@@ -14,6 +14,7 @@
use Api\Model\Shared\Dto\ActivityListDto;
use Api\Model\Shared\Rights\ProjectRoles;
use Api\Model\Shared\UserModel;
+use Api\Model\Shared\Command\UserCommands;
use PHPUnit\Framework\TestCase;
class ActivityListDtoTest extends TestCase
@@ -797,4 +798,67 @@ public function testGetActivityForUser_DeleteReplyToEntryComment_DtoAsExpected()
$this->assertEquals($expected["regarding"]["fieldValue"], $actual[ActivityModel::LEX_COMMENT_FIELD_VALUE]);
$this->assertEquals(["label" => "Sentence", "sense" => 2, "example" => 1], $actual["fieldLabel"]);
}
+
+ /** @throws Exception */
+ public function testGetActivityForProject_DeleteAccount_DtoAsExpected()
+ {
+ $environ = new LexiconMongoTestEnvironment();
+ $environ->clean();
+
+ $project = $environ->createProject(SF_TESTPROJECT, SF_TESTPROJECTCODE);
+ $projectId = $project->id->asString();
+
+ $this->assertEquals($project->appName, LfProjectModel::LEXICON_APP);
+
+ $userOneId = $environ->createUser("user1", "User One", "user1@email.com");
+ $project->addUser($userOneId, ProjectRoles::CONTRIBUTOR);
+ $userTwoId = $environ->createUser("user2", "User Two", "user2@email.com");
+ $project->addUser($userOneId, ProjectRoles::CONTRIBUTOR);
+ $project->write();
+
+ $entry = new LexEntryModel($project);
+ $entry->lexeme->form("en", "bank");
+ $sense1 = new LexSense();
+ $sense1->definition->form("en", "the sides of a river");
+ $entry->senses[] = $sense1;
+ $sense2 = new LexSense();
+ $sense2->definition->form("en", "a place to store money");
+ $entry->senses[] = $sense2;
+ $example1 = new LexExample();
+ $example1->sentence->form("en", "money in the bank");
+ $sense2->examples[] = $example1;
+ $example2 = new LexExample();
+ $example2->sentence->form("en", "a run on the bank");
+ $sense2->examples[] = $example2;
+ $entryId = $entry->write();
+
+ $regarding = [
+ "field" => "sentence",
+ "fieldNameForDisplay" => "Sentence",
+ "fieldValue" => "a run on the bank",
+ "inputSystem" => "en",
+ "word" => "bank",
+ "meaning" => "a place to store money",
+ ];
+ $data = [
+ "id" => "",
+ "entryRef" => $entryId,
+ "content" => "Comment on the sentence",
+ "regarding" => $regarding,
+ "contextGuid" => " sense#" . $sense2->guid . " example#" . $example1->guid . " sentence.en",
+ "isRegardingPicture" => false,
+ ];
+ $commentId = LexCommentCommands::updateComment($projectId, $userOneId, $data);
+
+ UserCommands::deleteAccounts([$userOneId], $userOneId);
+
+ $dto = ActivityListDto::getActivityForOneProject($project, $userTwoId);
+ $activity = $dto["activity"];
+ $activityRecord = array_shift($activity);
+ $this->assertArrayHasKey("userRef", $activityRecord);
+ $actual = $activityRecord["userRef"];
+
+ // After account is deleted, the username on the activity record should say "[Deleted User]"
+ $this->assertEquals("[Deleted User]", $actual["username"]);
+ }
}