diff --git a/build/.remarkrc b/.config/.remarkrc
similarity index 100%
rename from build/.remarkrc
rename to .config/.remarkrc
diff --git a/build/.yamllint b/.config/.yamllint
similarity index 100%
rename from build/.yamllint
rename to .config/.yamllint
diff --git a/.config/hadolint.yml b/.config/hadolint.yml
new file mode 100644
index 00000000..ee057ca0
--- /dev/null
+++ b/.config/hadolint.yml
@@ -0,0 +1,31 @@
+---
+# For all available rules see: https://github.com/hadolint/hadolint#rules
+ignored:
+ - DL3008 # We do not want to pin versions in apt get install.
+ - DL3018 # We do not want to pin versions in apk add
+
+# For full details see https://github.com/hadolint/hadolint#configure
+#
+# The following keys are available:
+#
+# failure-threshold: string # name of threshold level (error | warning | info | style | ignore | none)
+# format: string # Output format
+# # (tty | json | checkstyle | codeclimate | gitlab_codeclimate | gnu | codacy)
+# label-schema: # See https://github.com/hadolint/hadolint#linting-labels for details
+# author: string # Your name
+# contact: string # email address
+# created: timestamp # rfc3339 datetime
+# version: string # semver
+# documentation: string # url
+# git-revision: string # hash
+# license: string # spdx
+# no-color: boolean # true | false
+# no-fail: boolean # true | false
+# override:
+# error: [string] # list of rules
+# warning: [string] # list of rules
+# info: [string] # list of rules
+# style: [string] # list of rules
+# strict-labels: boolean # true | false
+# disable-ignore-pragma: boolean # true | false
+# trustedRegistries: string | [string] # registry or list of registries
diff --git a/build/phpcs.xml.dist b/.config/phpcs.xml.dist
similarity index 98%
rename from build/phpcs.xml.dist
rename to .config/phpcs.xml.dist
index fc1286ec..a6147742 100644
--- a/build/phpcs.xml.dist
+++ b/.config/phpcs.xml.dist
@@ -15,7 +15,7 @@
.
- */vendor/*|*/build/*
+ */vendor/*|*/.config/*
diff --git a/.github/workflows/dependancy-security-check.yml b/.github/workflows/dependancy-security-check.yml
deleted file mode 100644
index 285f62b6..00000000
--- a/.github/workflows/dependancy-security-check.yml
+++ /dev/null
@@ -1,50 +0,0 @@
----
-name: Security check
-
-on:
- - push
- - pull_request
- # Allow manually triggering the workflow.
- - workflow_dispatch
-
-# Cancels all previous workflow runs for the same branch that have not yet completed.
-concurrency:
- # The concurrency group contains the workflow name and the branch name.
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- security-check:
- runs-on: ubuntu-latest
- name: "Security check"
-
- strategy:
- matrix:
- php: ['8.2']
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v3
-
- - name: Install PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: ${{ matrix.php }}
- coverage: none
-
- # Install dependencies and handle caching in one go.
- # @link https://github.com/marketplace/actions/install-composer-dependencies
- - name: Install Composer dependencies
- uses: "ramsey/composer-install@v2"
- with:
- working-directory: "solid"
-
- - name: Download security checker
- # yamllint disable-line rule:line-length
- run: wget -P . https://github.com/fabpot/local-php-security-checker/releases/download/v2.0.4/local-php-security-checker_2.0.4_linux_amd64
-
- - name: Make security checker executable
- run: chmod +x ./local-php-security-checker_2.0.4_linux_amd64
-
- - name: Check against insecure dependencies
- run: ./local-php-security-checker_2.0.4_linux_amd64 --path=solid/composer.lock
diff --git a/.github/workflows/dockerfile.yml b/.github/workflows/dockerfile.yml
new file mode 100644
index 00000000..0fdbc00a
--- /dev/null
+++ b/.github/workflows/dockerfile.yml
@@ -0,0 +1,56 @@
+---
+name: Dockerfile Quality Assistance
+
+on:
+ # This event occurs when there is activity on a pull request. The workflow
+ # will be run against the commits, after merge to the target branch (main).
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '.config/hadolint.yml'
+ - '.dockerignore'
+ - '.github/workflows/dockerfile.yml'
+ - 'Dockerfile'
+ # Docker project specific, Dockerfile "COPY" and "ADD" entries.
+ - 'solid/'
+ - 'init-live.sh'
+ - 'init.sh'
+ - 'site.conf'
+ types: [ opened, reopened, synchronize ]
+ # This event occurs when there is a push to the repository.
+ push:
+ paths:
+ - '.config/hadolint.yml'
+ - '.dockerignore'
+ - '.github/workflows/dockerfile.yml'
+ - 'Dockerfile'
+ # Docker project specific, Dockerfile "COPY" and "ADD" entries.
+ - 'solid/'
+ - 'init-live.sh'
+ - 'init.sh'
+ - 'site.conf'
+ # Allow manually triggering the workflow.
+ workflow_dispatch:
+
+# Cancels all previous workflow runs for the same branch that have not yet completed.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ # Needed to allow the "concurrency" section to cancel a workflow run.
+ actions: write
+
+jobs:
+ # 03.quality.docker.lint.yml
+ lint-dockerfile:
+ name: Dockerfile Linting
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - uses: docker://pipelinecomponents/hadolint
+ with:
+ args: >-
+ hadolint
+ --config .config/hadolint.yml
+ Dockerfile
diff --git a/.github/workflows/json.yml b/.github/workflows/json.yml
new file mode 100644
index 00000000..7e83269e
--- /dev/null
+++ b/.github/workflows/json.yml
@@ -0,0 +1,46 @@
+---
+name: JSON Quality Assistance
+
+on:
+ # This event occurs when there is activity on a pull request. The workflow
+ # will be run against the commits, after merge to the target branch (main).
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '**.json'
+ - '.github/workflows/json.yml'
+ types: [ opened, reopened, synchronize ]
+ # This event occurs when there is a push to the repository.
+ push:
+ paths:
+ - '**.json'
+ - '.github/workflows/json.yml'
+ # Allow manually triggering the workflow.
+ workflow_dispatch:
+
+# Cancels all previous workflow runs for the same branch that have not yet completed.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ # Needed to allow the "concurrency" section to cancel a workflow run.
+ actions: write
+
+jobs:
+ # 01.preflight.json.lint-syntax.yml
+ lint-json-syntax:
+ name: JSON Syntax Linting
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - uses: docker://pipelinecomponents/jsonlint
+ with:
+ args: >-
+ find .
+ -not -path '*/.git/*'
+ -not -path '*/node_modules/*'
+ -not -path '*/vendor/*'
+ -name '*.json'
+ -type f
+ -exec jsonlint --quiet {} ;
diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml
deleted file mode 100644
index cbca0f41..00000000
--- a/.github/workflows/linting.yml
+++ /dev/null
@@ -1,37 +0,0 @@
----
-name: Linting jobs
-
-on:
- - push
- - pull_request
-
-jobs:
- lint-json:
- runs-on: ubuntu-20.04
- steps:
- - uses: actions/checkout@v2
- - uses: "docker://pipelinecomponents/jsonlint:latest"
- with:
- args: "find . -not -path './.git/*' -name '*.json' -type f"
-
- lint-php:
- runs-on: ubuntu-20.04
- steps:
- - uses: actions/checkout@v2
- - uses: pipeline-components/php-linter@master
-
- lint-markdown:
- runs-on: ubuntu-20.04
- steps:
- - uses: actions/checkout@v2
- - uses: pipeline-components/remark-lint@master
- with:
- options: --rc-path=build/.remarkrc --ignore-pattern='*/vendor/*'
-
- lint-yaml:
- runs-on: ubuntu-20.04
- steps:
- - uses: actions/checkout@v2
- - uses: pipeline-components/yamllint@master
- with:
- options: --config-file=build/.yamllint
diff --git a/.github/workflows/markdown.yml b/.github/workflows/markdown.yml
new file mode 100644
index 00000000..581b9c7e
--- /dev/null
+++ b/.github/workflows/markdown.yml
@@ -0,0 +1,42 @@
+---
+name: Markdown Quality Assistance
+
+on:
+ # This event occurs when there is activity on a pull request. The workflow
+ # will be run against the commits, after merge to the target branch (main).
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '**.md'
+ - '.github/workflows/markdown.yml'
+ types: [ opened, reopened, synchronize ]
+ # This event occurs when there is a push to the repository.
+ push:
+ paths:
+ - '**.md'
+ - '.github/workflows/markdown.yml'
+ # Allow manually triggering the workflow.
+ workflow_dispatch:
+
+# Cancels all previous workflow runs for the same branch that have not yet completed.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ # Needed to allow the "concurrency" section to cancel a workflow run.
+ actions: write
+
+jobs:
+ # 01.quality.markdown.lint-syntax.yml
+ lint-markdown-syntax:
+ name: Markdown Linting
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - uses: docker://pipelinecomponents/remark-lint
+ with:
+ args: >-
+ remark
+ --rc-path=.config/.remarkrc
+ --ignore-pattern='*/vendor/*'
diff --git a/.github/workflows/php-version-sniff.yml b/.github/workflows/php-version-sniff.yml
deleted file mode 100644
index 30cfd373..00000000
--- a/.github/workflows/php-version-sniff.yml
+++ /dev/null
@@ -1,31 +0,0 @@
----
-name: PHP Version Compatibility
-
-on:
- - push
- - pull_request
- # Allow manually triggering the workflow.
- - workflow_dispatch
-
-# Cancels all previous workflow runs for the same branch that have not yet completed.
-concurrency:
- # The concurrency group contains the workflow name and the branch name.
- group: ${{ github.workflow }}-${{ github.ref }}
- cancel-in-progress: true
-
-jobs:
- php-codesniffer:
- strategy:
- matrix:
- php: [ '8.1' ]
- runs-on: ubuntu-20.04
- steps:
- - uses: actions/checkout@v3
- - uses: pipeline-components/php-codesniffer@master
- with:
- options: >-
- -s
- --ignore='*vendor/*'
- --standard=PHPCompatibility
- --extensions=php
- --runtime-set testVersion ${{ matrix.php }}
diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
new file mode 100644
index 00000000..19f6071b
--- /dev/null
+++ b/.github/workflows/php.yml
@@ -0,0 +1,122 @@
+---
+name: PHP Quality Assistance
+
+on:
+ # This event occurs when there is activity on a pull request. The workflow
+ # will be run against the commits, after merge to the target branch (main).
+ pull_request:
+ paths:
+ - '**.php'
+ - '.config/phpcs.xml.dist'
+ - '.config/phpunit.xml.dist'
+ - '.github/workflows/php.yml'
+ - 'composer.json'
+ - 'composer.lock'
+ branches: [ main ]
+ types: [ opened, reopened, synchronize ]
+ # This event occurs when there is a push to the repository.
+ push:
+ paths:
+ - '**.php'
+ - '.config/phpcs.xml.dist'
+ - '.config/phpunit.xml.dist'
+ - '.github/workflows/php.yml'
+ - 'composer.json'
+ - 'composer.lock'
+ # Allow manually triggering the workflow.
+ workflow_dispatch:
+
+# Cancels all previous workflow runs for the same branch that have not yet completed.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ # Needed to allow the "concurrency" section to cancel a workflow run.
+ actions: write
+
+jobs:
+ # 01.preflight.php.lint-syntax.yml
+ lint-php-syntax:
+ name: PHP Syntax Linting
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - uses: docker://pipelinecomponents/php-linter
+ with:
+ args: >-
+ parallel-lint
+ --exclude .git
+ --exclude vendor
+ --no-progress
+ .
+ # 01.quality.php.validate.dependencies-file.yml
+ validate-dependencies-file:
+ name: Validate dependencies file
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - run: >-
+ composer validate
+ --check-lock
+ --no-plugins
+ --no-scripts
+ --strict
+ working-directory: "solid"
+ # 03.quality.php.scan.dependencies-vulnerabilities.yml
+ scan-dependencies-vulnerabilities:
+ name: Scan Dependencies Vulnerabilities
+ needs:
+ - validate-dependencies-file
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - run: >-
+ composer audit
+ --abandoned=report
+ --locked
+ --no-dev
+ --no-plugins
+ --no-scripts
+ working-directory: "solid"
+ # 03.quality.php.lint-quality.yml
+ php-lint-quality:
+ needs:
+ - lint-php-syntax
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - uses: docker://pipelinecomponents/php-codesniffer
+ with:
+ args: >-
+ phpcs
+ -s
+ --extensions=php
+ --ignore='*vendor/*'
+ --standard=.config/phpcs.xml.dist
+ .
+ # 03.quality.php.lint-version-compatibility.yml
+ php-check-version-compatibility:
+ name: PHP Version Compatibility
+ needs:
+ - lint-php-syntax
+ runs-on: ubuntu-24.04
+ strategy:
+ fail-fast: false
+ matrix:
+ php:
+ - '8.1' # from 2021-11 to 2023-11 (2025-12)
+ - '8.2' # from 2022-12 to 2024-12 (2026-12)
+ - '8.3' # from 2023-11 to 2025-12 (2027-12)
+ steps:
+ - uses: actions/checkout@v4
+ - uses: docker://pipelinecomponents/php-codesniffer
+ with:
+ args: >-
+ phpcs
+ -s
+ --extensions=php
+ --ignore='*vendor/*'
+ --runtime-set testVersion ${{ matrix.php }}
+ --standard=PHPCompatibility
+ .
diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml
deleted file mode 100644
index 885c567a..00000000
--- a/.github/workflows/quality-checks.yml
+++ /dev/null
@@ -1,23 +0,0 @@
----
-name: Quality Assurance jobs
-
-on:
- - push
- - pull_request
-
-jobs:
- composer-validate:
- runs-on: ubuntu-20.04
- steps:
- - uses: actions/checkout@v2
- - uses: "docker://composer"
- with:
- args: composer validate --strict --working-dir=solid/
-
- php-codesniffer:
- runs-on: ubuntu-20.04
- steps:
- - uses: actions/checkout@v2
- - uses: pipeline-components/php-codesniffer@master
- with:
- options: --standard=build/phpcs.xml.dist
diff --git a/.github/workflows/shell.yml b/.github/workflows/shell.yml
new file mode 100644
index 00000000..3ef3d51d
--- /dev/null
+++ b/.github/workflows/shell.yml
@@ -0,0 +1,60 @@
+---
+name: Shell Script Quality Assistance
+
+on:
+ # This event occurs when there is activity on a pull request. The workflow
+ # will be run against the commits, after merge to the target branch (main).
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '**.bash'
+ - '**.sh'
+ - '.github/workflows/shell.yml'
+ types: [ opened, reopened, synchronize ]
+ # This event occurs when there is a push to the repository.
+ push:
+ paths:
+ - '**.bash'
+ - '**.sh'
+ - '.github/workflows/shell.yml'
+ # Allow manually triggering the workflow.
+ workflow_dispatch:
+
+# Cancels all previous workflow runs for the same branch that have not yet completed.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ # Needed to allow the "concurrency" section to cancel a workflow run.
+ actions: write
+
+jobs:
+ # 01.preflight.shell.lint-syntax.yml
+ lint-shell-syntax:
+ name: Shell Syntax Linting
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - run: >-
+ find .
+ -name '*.sh'
+ -not -path '*/.git/*'
+ -type f
+ -print0
+ | xargs -0 -P"$(nproc)" -I{} bash -n "{}"
+ # 03.quality.shell.lint.yml
+ lint-shell-quality:
+ name: Shell Quality Linting
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - uses: docker://pipelinecomponents/shellcheck
+ with:
+ args: >-
+ find .
+ -not -path '*/.git/*'
+ -name '*.sh'
+ -type f
+ -print0
+ | xargs -0 -r -n1 shellcheck
diff --git a/.github/workflows/ci.yml b/.github/workflows/solid-tests-suites.yml
similarity index 90%
rename from .github/workflows/ci.yml
rename to .github/workflows/solid-tests-suites.yml
index 4aa46902..739960d4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/solid-tests-suites.yml
@@ -28,15 +28,16 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- # For the latest version information see: https://github.com/nextcloud/server/wiki/Maintenance-and-Release-Schedule
+ # For the latest version information see:
+ # https://github.com/nextcloud/server/wiki/Maintenance-and-Release-Schedule
# Versions before 22 are not tested as they run on PHP versions lower than 8.0
# Versions before 24 are not tested as they do not support `.well-known` entries
# Version 24 comes with PHP 8.0, which is no longer supported;
# Latest is not tested here, as that could cause failures unrelated to project changes
nextcloud_version:
- - 28
- 29
- 30
+ - 31
steps:
- name: Create docker tag from git reference
@@ -46,15 +47,15 @@ jobs:
| tr --complement --squeeze-repeats '[:alnum:]._-' '_')" \
>> "${GITHUB_ENV}"
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
id: cache-solid-nextcloud-docker
with:
path: cache/solid-nextcloud
key: solid-nextcloud-docker-${{ matrix.nextcloud_version }}-${{ github.sha }}
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- - uses: docker/login-action@v2
+ - uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -69,7 +70,8 @@ jobs:
.
docker push "ghcr.io/pdsinterop/solid-nextcloud:${{ env.TAG }}"
mkdir -p cache/solid-nextcloud
- docker image save solid-nextcloud:${{ env.TAG }} --output ./cache/solid-nextcloud/${{ github.sha }}-${{ matrix.nextcloud_version }}.tar
+ docker image save solid-nextcloud:${{ env.TAG }} \
+ --output ./cache/solid-nextcloud/${{ github.sha }}-${{ matrix.nextcloud_version }}.tar
solid-testsuite:
timeout-minutes: 30
@@ -82,16 +84,16 @@ jobs:
fail-fast: false
matrix:
nextcloud_version:
- - 28
- 29
- 30
+ - 31
test:
- 'solidtestsuite/solid-crud-tests:v7.0.5'
- 'solidtestsuite/web-access-control-tests:v7.1.0'
- 'solidtestsuite/webid-provider-tests:v2.1.1'
# Prevent EOL or non-stable versions of Nextcloud to fail the test-suite
- continue-on-error: ${{ contains(fromJson('[28,29,30]'), matrix.nextcloud_version) == false }}
+ continue-on-error: ${{ contains(fromJson('[29,30,31]'), matrix.nextcloud_version) == false }}
steps:
- name: Create docker tag from git reference
@@ -101,15 +103,15 @@ jobs:
| tr --complement --squeeze-repeats '[:alnum:]._-' '_')" \
>> "${GITHUB_ENV}"
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
id: cache-solid-nextcloud-docker
with:
path: cache/solid-nextcloud
key: solid-nextcloud-docker-${{ matrix.nextcloud_version }}-${{ github.sha }}
- - uses: docker/login-action@v2
+ - uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
diff --git a/.github/workflows/xml.yml b/.github/workflows/xml.yml
new file mode 100644
index 00000000..62d0c2eb
--- /dev/null
+++ b/.github/workflows/xml.yml
@@ -0,0 +1,45 @@
+---
+name: XML Quality Assistance
+
+on:
+ # This event occurs when there is activity on a pull request. The workflow
+ # will be run against the commits, after merge to the target branch (main).
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '**.xml'
+ - '**.xml.dist'
+ - '.github/workflows/xml.yml'
+ types: [ opened, reopened, synchronize ]
+ # This event occurs when there is a push to the repository.
+ push:
+ paths:
+ - '**.xml'
+ - '**.xml.dist'
+ - '.github/workflows/xml.yml'
+ # Allow manually triggering the workflow.
+ workflow_dispatch:
+
+# Cancels all previous workflow runs for the same branch that have not yet completed.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ # Needed to allow the "concurrency" section to cancel a workflow run.
+ actions: write
+
+jobs:
+ # 01.preflight.xml.lint-syntax.yml
+ lint-xml:
+ name: XML Linting
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - uses: docker://pipelinecomponents/xmllint
+ with:
+ args: >-
+ find .
+ -iname '*.xml'
+ -type f
+ -exec xmllint --noout {} \+
diff --git a/.github/workflows/yaml.yml b/.github/workflows/yaml.yml
new file mode 100644
index 00000000..ad8fb9d3
--- /dev/null
+++ b/.github/workflows/yaml.yml
@@ -0,0 +1,42 @@
+---
+name: YAML Quality Assistance
+
+on:
+ # This event occurs when there is activity on a pull request. The workflow
+ # will be run against the commits, after merge to the target branch (main).
+ pull_request:
+ branches: [ main ]
+ paths:
+ - '**.yml'
+ - '**.yaml'
+ types: [ opened, reopened, synchronize ]
+ # This event occurs when there is a push to the repository.
+ push:
+ paths:
+ - '**.yml'
+ - '**.yaml'
+ # Allow manually triggering the workflow.
+ workflow_dispatch:
+
+# Cancels all previous workflow runs for the same branch that have not yet completed.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ # Needed to allow the "concurrency" section to cancel a workflow run.
+ actions: write
+
+jobs:
+ # 01.preflight.yaml.lint.yml
+ lint-yaml:
+ name: YAML Linting
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v4
+ - uses: docker://pipelinecomponents/yamllint
+ with:
+ args: >-
+ yamllint
+ --config-file=.config/.yamllint
+ .
diff --git a/Dockerfile b/Dockerfile
index 80ff378e..84340bee 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,8 @@
-ARG NEXTCLOUD_VERSION
-FROM nextcloud:${NEXTCLOUD_VERSION}
+#ARG NEXTCLOUD_VERSION
+#FROM nextcloud:${NEXTCLOUD_VERSION}
+FROM nextcloud:31
-RUN apt-get update && apt-get install -yq \
+RUN apt-get update && apt-get install --no-install-recommends -yq \
git \
sudo \
vim \
diff --git a/env-vars-server.list b/env-vars-server.list
index 28239ba7..1f38d6fe 100644
--- a/env-vars-server.list
+++ b/env-vars-server.list
@@ -1,6 +1,6 @@
SERVER_ROOT=https://server
-STORAGE_ROOT=https://server/apps/solid/@alice/storage/
-ALICE_WEBID=https://server/apps/solid/@alice/profile/card#me
+STORAGE_ROOT=https://server/apps/solid/~alice/storage/
+ALICE_WEBID=https://server/apps/solid/~alice/profile/card#me
COOKIE_TYPE=nextcloud-compatible
USERNAME=alice
PASSWORD=alice123
diff --git a/env-vars-testers.list b/env-vars-testers.list
index 366167e0..a8a79563 100644
--- a/env-vars-testers.list
+++ b/env-vars-testers.list
@@ -1,11 +1,11 @@
-WEBID_ALICE=https://server/apps/solid/@alice/profile/card#me
+WEBID_ALICE=https://server/apps/solid/~alice/profile/card#me
OIDC_ISSUER_ALICE=https://server
-STORAGE_ROOT_ALICE=https://server/apps/solid/@alice/storage/
-WEBID_BOB=https://thirdparty/apps/solid/@alice/profile/card#me
+STORAGE_ROOT_ALICE=https://server/apps/solid/~alice/storage/
+WEBID_BOB=https://thirdparty/apps/solid/~alice/profile/card#me
OIDC_ISSUER_BOB=https://thirdparty
STORAGE_ROOT_BOB=https://thirdparty/
-ALICE_WEBID=https://server/apps/solid/@alice/profile/card#me
+ALICE_WEBID=https://server/apps/solid/~alice/profile/card#me
SERVER_ROOT_ESCAPED=https:\/\/server
SERVER_ROOT=https://server
-STORAGE_ROOT=https://server/apps/solid/@alice/storage/
+STORAGE_ROOT=https://server/apps/solid/~alice/storage/
SKIP_CONC=1
diff --git a/env-vars-thirdparty.list b/env-vars-thirdparty.list
index 9a2c8416..1c889484 100644
--- a/env-vars-thirdparty.list
+++ b/env-vars-thirdparty.list
@@ -1,5 +1,5 @@
SERVER_ROOT=https://thirdparty
-ALICE_WEBID=https://thirdparty/apps/solid/@alice/profile/card#me
+ALICE_WEBID=https://thirdparty/apps/solid/~alice/profile/card#me
COOKIE_TYPE=nextcloud-compatible
USERNAME=alice
PASSWORD=alice123
diff --git a/env.list b/env.list
index 1256e61d..cef5c00e 100644
--- a/env.list
+++ b/env.list
@@ -1,2 +1,2 @@
-ALICE_WEBID=https://server/apps/solid/@alice/profile/card#me
+ALICE_WEBID=https://server/apps/solid/~alice/profile/card#me
COOKIE_TYPE=nextcloud-compatible
diff --git a/run-solid-test-suite.sh b/run-solid-test-suite.sh
index 4416b4cb..9f35e320 100755
--- a/run-solid-test-suite.sh
+++ b/run-solid-test-suite.sh
@@ -2,7 +2,7 @@
set -e
-# Note that .github/workflows/ci.yml does not use this, this function is just for manual runs of this script.
+# Note that .github/workflows/solid-tests-suites.yml does not use this, this function is just for manual runs of this script.
# You can pick different values for the NEXTCLOUD_VERSION build arg, as required:
function setup {
docker build -t pubsub-server https://github.com/pdsinterop/php-solid-pubsub-server.git#main
diff --git a/site.conf b/site.conf
index d7789bd9..2f1272a1 100644
--- a/site.conf
+++ b/site.conf
@@ -1,4 +1,8 @@
+ # To use User SubDomains, make sure to add a "catch-all", for instance:
+ # ServerName nextcloud.local
+ # ServerAlias *.nextcloud.local
+
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
diff --git a/solid/appinfo/info.xml b/solid/appinfo/info.xml
index 16cc1db6..c19f2732 100644
--- a/solid/appinfo/info.xml
+++ b/solid/appinfo/info.xml
@@ -11,14 +11,14 @@ It supports the webid-oidc-dpop-pkce login flow to connect to a Solid App with y
When you do this, the Solid App can store data in your Nextcloud account through the Solid protocol.
]]>
- 0.9.1
+ 0.9.2agplAuke van SlootenSolidintegrationhttps://github.com/pdsinterop/solid-nextcloud/issues
-
+ OCA\Solid\Settings\SolidAdmin
diff --git a/solid/appinfo/routes.php b/solid/appinfo/routes.php
index 085cf09a..5475341a 100644
--- a/solid/appinfo/routes.php
+++ b/solid/appinfo/routes.php
@@ -7,58 +7,93 @@
* The controller class has to be registered in the application.php file since
* it's instantiated in there
*/
+
+use OC\AllConfig;
+use OC\AppConfig;
+use OCA\Solid\AppInfo\Application;
+use OCP\IConfig;
+use OCP\IRequest;
+
+$routes = [
+ ['name' => 'page#approval', 'url' => '/sharing/{clientId}', 'verb' => 'GET'],
+ ['name' => 'page#handleRevoke', 'url' => '/revoke/{clientId}', 'verb' => 'DELETE'],
+ ['name' => 'page#handleRevoke', 'url' => '/revoke/{clientId}', 'verb' => 'POST'],
+
+ ['name' => 'page#handleApproval', 'url' => '/sharing/{clientId}', 'verb' => 'POST'],
+ ['name' => 'page#customscheme', 'url' => '/customscheme', 'verb' => 'GET'],
+
+ ['name' => 'server#cors', 'url' => '/{path}', 'verb' => 'OPTIONS', 'requirements' => ['path' => '.+']],
+ ['name' => 'server#authorize', 'url' => '/authorize', 'verb' => 'GET'],
+ ['name' => 'server#jwks', 'url' => '/jwks', 'verb' => 'GET'],
+ ['name' => 'server#session', 'url' => '/session', 'verb' => 'GET'],
+ ['name' => 'server#logout', 'url' => '/logout', 'verb' => 'GET'],
+ ['name' => 'server#token', 'url' => '/token', 'verb' => 'POST'],
+ ['name' => 'server#userinfo', 'url' => '/userinfo', 'verb' => 'GET'],
+ ['name' => 'server#register', 'url' => '/register', 'verb' => 'POST'],
+ ['name' => 'server#registeredClient', 'url' => '/register/{clientId}', 'verb' => 'GET'],
+
+ ['name' => 'solidWebhook#listWebhooks', 'url' => '/webhook/list', 'verb' => 'GET'],
+ ['name' => 'solidWebhook#register', 'url' => '/webhook/register', 'verb' => 'POST'],
+ ['name' => 'solidWebhook#unregister', 'url' => '/webhook/unregister', 'verb' => 'POST'],
+
+ ['name' => 'solidWebsocket#register', 'url' => '/websocket/register', 'verb' => 'POST'],
+
+ ['name' => 'app#appLauncher', 'url' => '/', 'verb' => 'GET'],
+];
+
+$userIdRoutes = [
+ ['name' => 'page#profile', 'url' => '/~{userId}/', 'verb' => 'GET'],
+
+ ['name' => 'profile#handleGet', 'url' => '/~{userId}/profile{path}', 'verb' => 'GET', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'profile#handlePut', 'url' => '/~{userId}/profile{path}', 'verb' => 'PUT', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'profile#handlePatch', 'url' => '/~{userId}/profile{path}', 'verb' => 'PATCH', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'profile#handleHead', 'url' => '/~{userId}/profile{path}', 'verb' => 'HEAD', 'requirements' => ['path' => '.+'], ],
+
+ ['name' => 'storage#handleGet', 'url' => '/~{userId}/storage{path}', 'verb' => 'GET', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'storage#handlePost', 'url' => '/~{userId}/storage{path}', 'verb' => 'POST', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'storage#handlePut', 'url' => '/~{userId}/storage{path}', 'verb' => 'PUT', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'storage#handleDelete', 'url' => '/~{userId}/storage{path}', 'verb' => 'DELETE', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'storage#handlePatch', 'url' => '/~{userId}/storage{path}', 'verb' => 'PATCH', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'storage#handleHead', 'url' => '/~{userId}/storage{path}', 'verb' => 'HEAD', 'requirements' => ['path' => '.+'], ],
+
+ ['name' => 'calendar#handleGet', 'url' => '/~{userId}/calendar{path}', 'verb' => 'GET', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'calendar#handlePost', 'url' => '/~{userId}/calendar{path}', 'verb' => 'POST', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'calendar#handlePut', 'url' => '/~{userId}/calendar{path}', 'verb' => 'PUT', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'calendar#handleDelete', 'url' => '/~{userId}/calendar{path}', 'verb' => 'DELETE', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'calendar#handlePatch', 'url' => '/~{userId}/calendar{path}', 'verb' => 'PATCH', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'calendar#handleHead', 'url' => '/~{userId}/calendar{path}', 'verb' => 'HEAD', 'requirements' => ['path' => '.+'], ],
+
+ ['name' => 'contacts#handleGet', 'url' => '/~{userId}/contacts{path}', 'verb' => 'GET', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'contacts#handlePost', 'url' => '/~{userId}/contacts{path}', 'verb' => 'POST', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'contacts#handlePut', 'url' => '/~{userId}/contacts{path}', 'verb' => 'PUT', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'contacts#handleDelete', 'url' => '/~{userId}/contacts{path}', 'verb' => 'DELETE', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'contacts#handlePatch', 'url' => '/~{userId}/contacts{path}', 'verb' => 'PATCH', 'requirements' => ['path' => '.+'], ],
+ ['name' => 'contacts#handleHead', 'url' => '/~{userId}/contacts{path}', 'verb' => 'HEAD', 'requirements' => ['path' => '.+'], ],
+];
+
+// @TODO: All routes NOT generated by the UrlGenerator ANYWHERE in the code need to be checked!
+
+if (Application::$userSubDomainsEnabled) {
+ $userIdRoutes = array_map(function ($route) {
+ if ($route['name'] === 'page#profile') {
+ // The profile route should be `/me` instead of `/~{userId}/`
+ $route['url'] = '/me';
+ } else {
+ // When UserSubDomains are enabled, all routes that start with
+ // `/~{userId}/` should just be `/`, as the userId is present
+ // in the subdomain.
+ $route['url'] = preg_replace('#^/~{userId}/#', '/', $route['url']);
+ }
+
+ // The required userId is set to the userId from the subdomain
+ $host = OC::$server->get(IRequest::class)->getServerHost();
+ $userId = explode('.', $host)[0];
+ $route['defaults'] = ['userId' => $userId];
+
+ return $route;
+ }, $userIdRoutes);
+}
+
return [
- 'routes' => [
- ['name' => 'page#profile', 'url' => '/@{userId}/', 'verb' => 'GET'],
- ['name' => 'page#approval', 'url' => '/sharing/{clientId}', 'verb' => 'GET'],
- ['name' => 'page#handleRevoke', 'url' => '/revoke/{clientId}', 'verb' => 'DELETE'],
- ['name' => 'page#handleRevoke', 'url' => '/revoke/{clientId}', 'verb' => 'POST'],
-
- ['name' => 'page#handleApproval', 'url' => '/sharing/{clientId}', 'verb' => 'POST'],
- ['name' => 'page#customscheme', 'url' => '/customscheme', 'verb' => 'GET'],
-
- ['name' => 'server#cors', 'url' => '/{path}', 'verb' => 'OPTIONS', 'requirements' => array('path' => '.+') ],
- ['name' => 'server#authorize', 'url' => '/authorize', 'verb' => 'GET'],
- ['name' => 'server#jwks', 'url' => '/jwks', 'verb' => 'GET'],
- ['name' => 'server#session', 'url' => '/session', 'verb' => 'GET'],
- ['name' => 'server#logout', 'url' => '/logout', 'verb' => 'GET'],
- ['name' => 'server#token', 'url' => '/token', 'verb' => 'POST'],
- ['name' => 'server#userinfo', 'url' => '/userinfo', 'verb' => 'GET'],
- ['name' => 'server#register', 'url' => '/register', 'verb' => 'POST'],
- ['name' => 'server#registeredClient', 'url' => '/register/{clientId}', 'verb' => 'GET'],
-
- ['name' => 'profile#handleGet', 'url' => '/@{userId}/profile{path}', 'verb' => 'GET', 'requirements' => array('path' => '.+')],
- ['name' => 'profile#handlePut', 'url' => '/@{userId}/profile{path}', 'verb' => 'PUT', 'requirements' => array('path' => '.+')],
- ['name' => 'profile#handlePatch', 'url' => '/@{userId}/profile{path}', 'verb' => 'PATCH', 'requirements' => array('path' => '.+')],
- ['name' => 'profile#handleHead', 'url' => '/@{userId}/profile{path}', 'verb' => 'HEAD', 'requirements' => array('path' => '.+')],
-
- ['name' => 'storage#handleGet', 'url' => '/@{userId}/storage{path}', 'verb' => 'GET', 'requirements' => array('path' => '.+')],
- ['name' => 'storage#handlePost', 'url' => '/@{userId}/storage{path}', 'verb' => 'POST', 'requirements' => array('path' => '.+')],
- ['name' => 'storage#handlePut', 'url' => '/@{userId}/storage{path}', 'verb' => 'PUT', 'requirements' => array('path' => '.+')],
- ['name' => 'storage#handleDelete', 'url' => '/@{userId}/storage{path}', 'verb' => 'DELETE', 'requirements' => array('path' => '.+')],
- ['name' => 'storage#handlePatch', 'url' => '/@{userId}/storage{path}', 'verb' => 'PATCH', 'requirements' => array('path' => '.+')],
- ['name' => 'storage#handleHead', 'url' => '/@{userId}/storage{path}', 'verb' => 'HEAD', 'requirements' => array('path' => '.+')],
-
- ['name' => 'calendar#handleGet', 'url' => '/@{userId}/calendar{path}', 'verb' => 'GET', 'requirements' => array('path' => '.+')],
- ['name' => 'calendar#handlePost', 'url' => '/@{userId}/calendar{path}', 'verb' => 'POST', 'requirements' => array('path' => '.+')],
- ['name' => 'calendar#handlePut', 'url' => '/@{userId}/calendar{path}', 'verb' => 'PUT', 'requirements' => array('path' => '.+')],
- ['name' => 'calendar#handleDelete', 'url' => '/@{userId}/calendar{path}', 'verb' => 'DELETE', 'requirements' => array('path' => '.+')],
- ['name' => 'calendar#handlePatch', 'url' => '/@{userId}/calendar{path}', 'verb' => 'PATCH', 'requirements' => array('path' => '.+')],
- ['name' => 'calendar#handleHead', 'url' => '/@{userId}/calendar{path}', 'verb' => 'HEAD', 'requirements' => array('path' => '.+')],
-
- ['name' => 'contacts#handleGet', 'url' => '/@{userId}/contacts{path}', 'verb' => 'GET', 'requirements' => array('path' => '.+')],
- ['name' => 'contacts#handlePost', 'url' => '/@{userId}/contacts{path}', 'verb' => 'POST', 'requirements' => array('path' => '.+')],
- ['name' => 'contacts#handlePut', 'url' => '/@{userId}/contacts{path}', 'verb' => 'PUT', 'requirements' => array('path' => '.+')],
- ['name' => 'contacts#handleDelete', 'url' => '/@{userId}/contacts{path}', 'verb' => 'DELETE', 'requirements' => array('path' => '.+')],
- ['name' => 'contacts#handlePatch', 'url' => '/@{userId}/contacts{path}', 'verb' => 'PATCH', 'requirements' => array('path' => '.+')],
- ['name' => 'contacts#handleHead', 'url' => '/@{userId}/contacts{path}', 'verb' => 'HEAD', 'requirements' => array('path' => '.+')],
-
- ['name' => 'solidWebhook#listWebhooks', 'url' => '/webhook/list', 'verb' => 'GET'],
- ['name' => 'solidWebhook#register', 'url' => '/webhook/register', 'verb' => 'POST'],
- ['name' => 'solidWebhook#unregister', 'url' => '/webhook/unregister', 'verb' => 'POST'],
-
- ['name' => 'solidWebsocket#register', 'url' => '/websocket/register', 'verb' => 'POST'],
-
- ['name' => 'app#appLauncher', 'url' => '/', 'verb' => 'GET'],
- ]
+ 'routes' => array_merge($routes, $userIdRoutes),
];
diff --git a/solid/composer.json b/solid/composer.json
index 61dbb3b6..671c009f 100644
--- a/solid/composer.json
+++ b/solid/composer.json
@@ -30,15 +30,15 @@
"laminas/laminas-diactoros": "^2.8",
"lcobucci/jwt": "^4.1",
"pdsinterop/flysystem-nextcloud": "^0.2",
- "pdsinterop/flysystem-rdf": "^0.5",
- "pdsinterop/solid-auth": "v0.11.0",
- "pdsinterop/solid-crud": "^0.7.3",
+ "pdsinterop/flysystem-rdf": "^0.6",
+ "pdsinterop/solid-auth": "^0.12.2",
+ "pdsinterop/solid-crud": "^0.8",
"psr/log": "^1.1"
},
"require-dev": {
"doctrine/dbal": "*",
"nextcloud/server": "*",
- "phpunit/phpunit": "^8 || ^9"
+ "phpunit/phpunit": "^8.5.32"
},
"repositories": [
{
diff --git a/solid/composer.lock b/solid/composer.lock
index 40fd3303..49a582ca 100644
--- a/solid/composer.lock
+++ b/solid/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": "1843d50801f15c12e9fb50345b3bfb3b",
+ "content-hash": "630d8401030511a28cf54157d9bbd4cf",
"packages": [
{
"name": "arc/base",
@@ -1455,24 +1455,24 @@
},
{
"name": "pdsinterop/flysystem-rdf",
- "version": "v0.5.0",
+ "version": "v0.6.0",
"source": {
"type": "git",
"url": "https://github.com/pdsinterop/flysystem-rdf.git",
- "reference": "2a0b105f66c16b664bcd56f30d76f464b18be065"
+ "reference": "cb72c2a0538b2a552a9281f2bd9e4a7f48ca035d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pdsinterop/flysystem-rdf/zipball/2a0b105f66c16b664bcd56f30d76f464b18be065",
- "reference": "2a0b105f66c16b664bcd56f30d76f464b18be065",
+ "url": "https://api.github.com/repos/pdsinterop/flysystem-rdf/zipball/cb72c2a0538b2a552a9281f2bd9e4a7f48ca035d",
+ "reference": "cb72c2a0538b2a552a9281f2bd9e4a7f48ca035d",
"shasum": ""
},
"require": {
- "easyrdf/easyrdf": "^1.1.1",
"ext-mbstring": "*",
"league/flysystem": "^1.0",
"ml/json-ld": "^1.2",
- "php": "^8.0"
+ "php": "^8.0",
+ "sweetrdf/easyrdf": "^1.1"
},
"require-dev": {
"phpunit/phpunit": "^8|^9"
@@ -1490,22 +1490,22 @@
"description": "Flysystem plugin to transform RDF data between various serialization formats.",
"support": {
"issues": "https://github.com/pdsinterop/flysystem-rdf/issues",
- "source": "https://github.com/pdsinterop/flysystem-rdf/tree/v0.5.0"
+ "source": "https://github.com/pdsinterop/flysystem-rdf/tree/v0.6.0"
},
- "time": "2022-08-22T14:36:29+00:00"
+ "time": "2025-05-16T08:57:11+00:00"
},
{
"name": "pdsinterop/solid-auth",
- "version": "v0.11.0",
+ "version": "v0.12.2",
"source": {
"type": "git",
"url": "https://github.com/pdsinterop/php-solid-auth.git",
- "reference": "0c5f65b0a9340fe9d50bef9d0e279db54610ffac"
+ "reference": "1d1160ee0f7ca71d3e34151aea94232e1cfa49ff"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pdsinterop/php-solid-auth/zipball/0c5f65b0a9340fe9d50bef9d0e279db54610ffac",
- "reference": "0c5f65b0a9340fe9d50bef9d0e279db54610ffac",
+ "url": "https://api.github.com/repos/pdsinterop/php-solid-auth/zipball/1d1160ee0f7ca71d3e34151aea94232e1cfa49ff",
+ "reference": "1d1160ee0f7ca71d3e34151aea94232e1cfa49ff",
"shasum": ""
},
"require": {
@@ -1514,7 +1514,7 @@
"ext-openssl": "*",
"laminas/laminas-diactoros": "^2.8",
"lcobucci/jwt": "^4.1",
- "league/oauth2-server": "^8.3.5",
+ "league/oauth2-server": "^8.5.5",
"php": "^8.0",
"web-token/jwt-core": "^2.2"
},
@@ -1539,22 +1539,22 @@
"description": "OAuth2, OpenID and OIDC for Solid Server implementations.",
"support": {
"issues": "https://github.com/pdsinterop/php-solid-auth/issues",
- "source": "https://github.com/pdsinterop/php-solid-auth/tree/v0.11.0"
+ "source": "https://github.com/pdsinterop/php-solid-auth/tree/v0.12.2"
},
- "time": "2025-02-14T12:57:21+00:00"
+ "time": "2025-05-28T14:53:41+00:00"
},
{
"name": "pdsinterop/solid-crud",
- "version": "v0.7.3",
+ "version": "v0.8.0",
"source": {
"type": "git",
"url": "https://github.com/pdsinterop/php-solid-crud.git",
- "reference": "c5369ef7b46d3d77a7686c3f4531e818e1797e27"
+ "reference": "ca1421770b17c69cc5989ce6864e86405030a50c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pdsinterop/php-solid-crud/zipball/c5369ef7b46d3d77a7686c3f4531e818e1797e27",
- "reference": "c5369ef7b46d3d77a7686c3f4531e818e1797e27",
+ "url": "https://api.github.com/repos/pdsinterop/php-solid-crud/zipball/ca1421770b17c69cc5989ce6864e86405030a50c",
+ "reference": "ca1421770b17c69cc5989ce6864e86405030a50c",
"shasum": ""
},
"require": {
@@ -1562,7 +1562,7 @@
"laminas/laminas-diactoros": "^2.14",
"league/flysystem": "^1.0",
"mjrider/flysystem-factory": "^0.7",
- "pdsinterop/flysystem-rdf": "^0.5",
+ "pdsinterop/flysystem-rdf": "^0.6",
"php": "^8.0",
"pietercolpaert/hardf": "^0.3",
"psr/http-factory": "^1.0",
@@ -1586,9 +1586,9 @@
"description": "Solid HTTPS REST API specification compliant implementation for handling Resource CRUD",
"support": {
"issues": "https://github.com/pdsinterop/php-solid-crud/issues",
- "source": "https://github.com/pdsinterop/php-solid-crud/tree/v0.7.3"
+ "source": "https://github.com/pdsinterop/php-solid-crud/tree/v0.8.0"
},
- "time": "2024-01-17T10:48:57+00:00"
+ "time": "2025-05-16T09:04:57+00:00"
},
{
"name": "phrity/net-uri",
@@ -2128,6 +2128,82 @@
],
"time": "2020-11-03T09:10:25+00:00"
},
+ {
+ "name": "sweetrdf/easyrdf",
+ "version": "1.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sweetrdf/easyrdf.git",
+ "reference": "6952b79bd1818817f20d0c64de54c7ecd5a24947"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sweetrdf/easyrdf/zipball/6952b79bd1818817f20d0c64de54c7ecd5a24947",
+ "reference": "6952b79bd1818817f20d0c64de54c7ecd5a24947",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "ext-pcre": "*",
+ "ext-xmlreader": "*",
+ "lib-libxml": "*",
+ "php": "^7.1|^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.0",
+ "ml/json-ld": "^1.0",
+ "phpstan/phpstan": "^1.0",
+ "phpstan/phpstan-phpunit": "^1.0",
+ "phpunit/phpunit": "^7.5|^8.5|^9.5",
+ "semsol/arc2": "^2.4",
+ "zendframework/zend-http": "^2.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "EasyRdf\\": "lib"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nicholas Humfrey",
+ "email": "njh@aelius.com",
+ "homepage": "http://www.aelius.com/njh/",
+ "role": "Developer"
+ },
+ {
+ "name": "Alexey Zakhlestin",
+ "email": "indeyets@gmail.com",
+ "homepage": "http://indeyets.ru/",
+ "role": "Developer"
+ },
+ {
+ "name": "Konrad Abicht",
+ "email": "hi@inspirito.de",
+ "homepage": "http://inspirito.de/",
+ "role": "Maintainer, Developer"
+ }
+ ],
+ "description": "EasyRdf is a PHP library designed to make it easy to consume and produce RDF.",
+ "keywords": [
+ "Linked Data",
+ "RDF",
+ "Semantic Web",
+ "Turtle",
+ "rdfa",
+ "sparql"
+ ],
+ "support": {
+ "issues": "https://github.com/sweetrdf/easyrdf/issues",
+ "source": "https://github.com/sweetrdf/easyrdf/tree/1.7"
+ },
+ "time": "2022-09-19T07:53:57+00:00"
+ },
{
"name": "textalk/websocket",
"version": "1.6.3",
@@ -2366,26 +2442,29 @@
},
{
"name": "doctrine/deprecations",
- "version": "1.1.4",
+ "version": "1.1.5",
"source": {
"type": "git",
"url": "https://github.com/doctrine/deprecations.git",
- "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9"
+ "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9",
- "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9",
+ "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38",
+ "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
+ "conflict": {
+ "phpunit/phpunit": "<=7.5 || >=13"
+ },
"require-dev": {
- "doctrine/coding-standard": "^9 || ^12",
- "phpstan/phpstan": "1.4.10 || 2.0.3",
+ "doctrine/coding-standard": "^9 || ^12 || ^13",
+ "phpstan/phpstan": "1.4.10 || 2.1.11",
"phpstan/phpstan-phpunit": "^1.0 || ^2",
- "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12",
"psr/log": "^1 || ^2 || ^3"
},
"suggest": {
@@ -2405,36 +2484,36 @@
"homepage": "https://www.doctrine-project.org/",
"support": {
"issues": "https://github.com/doctrine/deprecations/issues",
- "source": "https://github.com/doctrine/deprecations/tree/1.1.4"
+ "source": "https://github.com/doctrine/deprecations/tree/1.1.5"
},
- "time": "2024-12-07T21:18:45+00:00"
+ "time": "2025-04-07T20:06:18+00:00"
},
{
"name": "doctrine/instantiator",
- "version": "2.0.0",
+ "version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
- "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0"
+ "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
- "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
+ "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
"shasum": ""
},
"require": {
- "php": "^8.1"
+ "php": "^7.1 || ^8.0"
},
"require-dev": {
- "doctrine/coding-standard": "^11",
+ "doctrine/coding-standard": "^9 || ^11",
"ext-pdo": "*",
"ext-phar": "*",
- "phpbench/phpbench": "^1.2",
- "phpstan/phpstan": "^1.9.4",
- "phpstan/phpstan-phpunit": "^1.3",
- "phpunit/phpunit": "^9.5.27",
- "vimeo/psalm": "^5.4"
+ "phpbench/phpbench": "^0.16 || ^1",
+ "phpstan/phpstan": "^1.4",
+ "phpstan/phpstan-phpunit": "^1",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+ "vimeo/psalm": "^4.30 || ^5.4"
},
"type": "library",
"autoload": {
@@ -2461,7 +2540,7 @@
],
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
- "source": "https://github.com/doctrine/instantiator/tree/2.0.0"
+ "source": "https://github.com/doctrine/instantiator/tree/1.5.0"
},
"funding": [
{
@@ -2477,20 +2556,20 @@
"type": "tidelift"
}
],
- "time": "2022-12-30T00:23:10+00:00"
+ "time": "2022-12-30T00:15:36+00:00"
},
{
"name": "myclabs/deep-copy",
- "version": "1.13.0",
+ "version": "1.13.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "024473a478be9df5fdaca2c793f2232fe788e414"
+ "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414",
- "reference": "024473a478be9df5fdaca2c793f2232fe788e414",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c",
+ "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c",
"shasum": ""
},
"require": {
@@ -2529,7 +2608,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
- "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0"
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1"
},
"funding": [
{
@@ -2537,7 +2616,7 @@
"type": "tidelift"
}
],
- "time": "2025-02-12T12:17:51+00:00"
+ "time": "2025-04-29T12:36:36+00:00"
},
{
"name": "nextcloud/server",
@@ -2561,64 +2640,6 @@
}
}
},
- {
- "name": "nikic/php-parser",
- "version": "v5.4.0",
- "source": {
- "type": "git",
- "url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "447a020a1f875a434d62f2a401f53b82a396e494"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494",
- "reference": "447a020a1f875a434d62f2a401f53b82a396e494",
- "shasum": ""
- },
- "require": {
- "ext-ctype": "*",
- "ext-json": "*",
- "ext-tokenizer": "*",
- "php": ">=7.4"
- },
- "require-dev": {
- "ircmaxell/php-yacc": "^0.0.7",
- "phpunit/phpunit": "^9.0"
- },
- "bin": [
- "bin/php-parse"
- ],
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "5.0-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "PhpParser\\": "lib/PhpParser"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Nikita Popov"
- }
- ],
- "description": "A PHP parser written in PHP",
- "keywords": [
- "parser",
- "php"
- ],
- "support": {
- "issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0"
- },
- "time": "2024-12-30T11:07:19+00:00"
- },
{
"name": "phar-io/manifest",
"version": "2.0.4",
@@ -2739,44 +2760,40 @@
},
{
"name": "phpunit/php-code-coverage",
- "version": "9.2.32",
+ "version": "7.0.17",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5"
+ "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5",
- "reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/40a4ed114a4aea5afd6df8d0f0c9cd3033097f66",
+ "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66",
"shasum": ""
},
"require": {
"ext-dom": "*",
- "ext-libxml": "*",
"ext-xmlwriter": "*",
- "nikic/php-parser": "^4.19.1 || ^5.1.0",
- "php": ">=7.3",
- "phpunit/php-file-iterator": "^3.0.6",
- "phpunit/php-text-template": "^2.0.4",
- "sebastian/code-unit-reverse-lookup": "^2.0.3",
- "sebastian/complexity": "^2.0.3",
- "sebastian/environment": "^5.1.5",
- "sebastian/lines-of-code": "^1.0.4",
- "sebastian/version": "^3.0.2",
- "theseer/tokenizer": "^1.2.3"
+ "php": ">=7.2",
+ "phpunit/php-file-iterator": "^2.0.2",
+ "phpunit/php-text-template": "^1.2.1",
+ "phpunit/php-token-stream": "^3.1.3 || ^4.0",
+ "sebastian/code-unit-reverse-lookup": "^1.0.1",
+ "sebastian/environment": "^4.2.2",
+ "sebastian/version": "^2.0.1",
+ "theseer/tokenizer": "^1.1.3"
},
"require-dev": {
- "phpunit/phpunit": "^9.6"
+ "phpunit/phpunit": "^8.2.2"
},
"suggest": {
- "ext-pcov": "PHP extension that provides line coverage",
- "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+ "ext-xdebug": "^2.7.2"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "9.2.x-dev"
+ "dev-master": "7.0-dev"
}
},
"autoload": {
@@ -2804,8 +2821,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
- "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.17"
},
"funding": [
{
@@ -2813,32 +2829,32 @@
"type": "github"
}
],
- "time": "2024-08-22T04:23:01+00:00"
+ "time": "2024-03-02T06:09:37+00:00"
},
{
"name": "phpunit/php-file-iterator",
- "version": "3.0.6",
+ "version": "2.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
- "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf"
+ "reference": "69deeb8664f611f156a924154985fbd4911eb36b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
- "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/69deeb8664f611f156a924154985fbd4911eb36b",
+ "reference": "69deeb8664f611f156a924154985fbd4911eb36b",
"shasum": ""
},
"require": {
- "php": ">=7.3"
+ "php": ">=7.1"
},
"require-dev": {
- "phpunit/phpunit": "^9.3"
+ "phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "2.0.x-dev"
}
},
"autoload": {
@@ -2865,7 +2881,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
- "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6"
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.6"
},
"funding": [
{
@@ -2873,38 +2889,26 @@
"type": "github"
}
],
- "time": "2021-12-02T12:48:52+00:00"
+ "time": "2024-03-01T13:39:50+00:00"
},
{
- "name": "phpunit/php-invoker",
- "version": "3.1.1",
+ "name": "phpunit/php-text-template",
+ "version": "1.2.1",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/php-invoker.git",
- "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67"
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
- "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"shasum": ""
},
"require": {
- "php": ">=7.3"
- },
- "require-dev": {
- "ext-pcntl": "*",
- "phpunit/phpunit": "^9.3"
- },
- "suggest": {
- "ext-pcntl": "*"
+ "php": ">=5.3.3"
},
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.1-dev"
- }
- },
"autoload": {
"classmap": [
"src/"
@@ -2921,47 +2925,41 @@
"role": "lead"
}
],
- "description": "Invoke callables with a timeout",
- "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
"keywords": [
- "process"
+ "template"
],
"support": {
- "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
- "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1"
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1"
},
- "funding": [
- {
- "url": "https://github.com/sebastianbergmann",
- "type": "github"
- }
- ],
- "time": "2020-09-28T05:58:55+00:00"
+ "time": "2015-06-21T13:50:34+00:00"
},
{
- "name": "phpunit/php-text-template",
- "version": "2.0.4",
+ "name": "phpunit/php-timer",
+ "version": "2.1.4",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/php-text-template.git",
- "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28"
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
- "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a691211e94ff39a34811abd521c31bd5b305b0bb",
+ "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb",
"shasum": ""
},
"require": {
- "php": ">=7.3"
+ "php": ">=7.1"
},
"require-dev": {
- "phpunit/phpunit": "^9.3"
+ "phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0-dev"
+ "dev-master": "2.1-dev"
}
},
"autoload": {
@@ -2980,14 +2978,14 @@
"role": "lead"
}
],
- "description": "Simple template engine.",
- "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
"keywords": [
- "template"
+ "timer"
],
"support": {
- "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
- "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4"
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.4"
},
"funding": [
{
@@ -2995,32 +2993,33 @@
"type": "github"
}
],
- "time": "2020-10-26T05:33:50+00:00"
+ "time": "2024-03-01T13:42:41+00:00"
},
{
- "name": "phpunit/php-timer",
- "version": "5.0.3",
+ "name": "phpunit/php-token-stream",
+ "version": "4.0.4",
"source": {
"type": "git",
- "url": "https://github.com/sebastianbergmann/php-timer.git",
- "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2"
+ "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+ "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
- "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3",
+ "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3",
"shasum": ""
},
"require": {
- "php": ">=7.3"
+ "ext-tokenizer": "*",
+ "php": "^7.3 || ^8.0"
},
"require-dev": {
- "phpunit/phpunit": "^9.3"
+ "phpunit/phpunit": "^9.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "5.0-dev"
+ "dev-master": "4.0-dev"
}
},
"autoload": {
@@ -3035,18 +3034,17 @@
"authors": [
{
"name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
+ "email": "sebastian@phpunit.de"
}
],
- "description": "Utility class for timing",
- "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "description": "Wrapper around PHP's tokenizer extension.",
+ "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
"keywords": [
- "timer"
+ "tokenizer"
],
"support": {
- "issues": "https://github.com/sebastianbergmann/php-timer/issues",
- "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3"
+ "issues": "https://github.com/sebastianbergmann/php-token-stream/issues",
+ "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master"
},
"funding": [
{
@@ -3054,54 +3052,53 @@
"type": "github"
}
],
- "time": "2020-10-26T13:16:10+00:00"
+ "abandoned": true,
+ "time": "2020-08-04T08:28:15+00:00"
},
{
"name": "phpunit/phpunit",
- "version": "9.6.22",
+ "version": "8.5.42",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c"
+ "reference": "3a68a70824da546d26ac08ca4fced67341f4158f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c",
- "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3a68a70824da546d26ac08ca4fced67341f4158f",
+ "reference": "3a68a70824da546d26ac08ca4fced67341f4158f",
"shasum": ""
},
"require": {
- "doctrine/instantiator": "^1.5.0 || ^2",
+ "doctrine/instantiator": "^1.5.0",
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
- "myclabs/deep-copy": "^1.12.1",
+ "myclabs/deep-copy": "^1.13.1",
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
- "php": ">=7.3",
- "phpunit/php-code-coverage": "^9.2.32",
- "phpunit/php-file-iterator": "^3.0.6",
- "phpunit/php-invoker": "^3.1.1",
- "phpunit/php-text-template": "^2.0.4",
- "phpunit/php-timer": "^5.0.3",
- "sebastian/cli-parser": "^1.0.2",
- "sebastian/code-unit": "^1.0.8",
- "sebastian/comparator": "^4.0.8",
- "sebastian/diff": "^4.0.6",
- "sebastian/environment": "^5.1.5",
- "sebastian/exporter": "^4.0.6",
- "sebastian/global-state": "^5.0.7",
- "sebastian/object-enumerator": "^4.0.4",
- "sebastian/resource-operations": "^3.0.4",
- "sebastian/type": "^3.2.1",
- "sebastian/version": "^3.0.2"
+ "php": ">=7.2",
+ "phpunit/php-code-coverage": "^7.0.17",
+ "phpunit/php-file-iterator": "^2.0.6",
+ "phpunit/php-text-template": "^1.2.1",
+ "phpunit/php-timer": "^2.1.4",
+ "sebastian/comparator": "^3.0.5",
+ "sebastian/diff": "^3.0.6",
+ "sebastian/environment": "^4.2.5",
+ "sebastian/exporter": "^3.1.6",
+ "sebastian/global-state": "^3.0.5",
+ "sebastian/object-enumerator": "^3.0.5",
+ "sebastian/resource-operations": "^2.0.3",
+ "sebastian/type": "^1.1.5",
+ "sebastian/version": "^2.0.1"
},
"suggest": {
"ext-soap": "To be able to generate mocks based on WSDL files",
- "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage",
+ "phpunit/php-invoker": "To allow enforcing time limits"
},
"bin": [
"phpunit"
@@ -3109,13 +3106,10 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "9.6-dev"
+ "dev-master": "8.5-dev"
}
},
"autoload": {
- "files": [
- "src/Framework/Assert/Functions.php"
- ],
"classmap": [
"src/"
]
@@ -3141,7 +3135,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.42"
},
"funding": [
{
@@ -3153,148 +3147,44 @@
"type": "github"
},
{
- "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
- "type": "tidelift"
- }
- ],
- "time": "2024-12-05T13:48:26+00:00"
- },
- {
- "name": "sebastian/cli-parser",
- "version": "1.0.2",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/cli-parser.git",
- "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
- "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
- "shasum": ""
- },
- "require": {
- "php": ">=7.3"
- },
- "require-dev": {
- "phpunit/phpunit": "^9.3"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "Library for parsing CLI options",
- "homepage": "https://github.com/sebastianbergmann/cli-parser",
- "support": {
- "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
- "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2"
- },
- "funding": [
- {
- "url": "https://github.com/sebastianbergmann",
- "type": "github"
- }
- ],
- "time": "2024-03-02T06:27:43+00:00"
- },
- {
- "name": "sebastian/code-unit",
- "version": "1.0.8",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/code-unit.git",
- "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120",
- "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120",
- "shasum": ""
- },
- "require": {
- "php": ">=7.3"
- },
- "require-dev": {
- "phpunit/phpunit": "^9.3"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
{
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "Collection of value objects that represent the PHP code units",
- "homepage": "https://github.com/sebastianbergmann/code-unit",
- "support": {
- "issues": "https://github.com/sebastianbergmann/code-unit/issues",
- "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8"
- },
- "funding": [
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
{
- "url": "https://github.com/sebastianbergmann",
- "type": "github"
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+ "type": "tidelift"
}
],
- "time": "2020-10-26T13:08:54+00:00"
+ "time": "2025-05-02T06:33:00+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
- "version": "2.0.3",
+ "version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
- "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5"
+ "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
- "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54",
+ "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54",
"shasum": ""
},
"require": {
- "php": ">=7.3"
+ "php": ">=5.6"
},
"require-dev": {
- "phpunit/phpunit": "^9.3"
+ "phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0-dev"
+ "dev-master": "1.0.x-dev"
}
},
"autoload": {
@@ -3316,7 +3206,7 @@
"homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
"support": {
"issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
- "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3"
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.3"
},
"funding": [
{
@@ -3324,34 +3214,34 @@
"type": "github"
}
],
- "time": "2020-09-28T05:30:19+00:00"
+ "time": "2024-03-01T13:45:45+00:00"
},
{
"name": "sebastian/comparator",
- "version": "4.0.8",
+ "version": "3.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "fa0f136dd2334583309d32b62544682ee972b51a"
+ "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a",
- "reference": "fa0f136dd2334583309d32b62544682ee972b51a",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dc7ceb4a24aede938c7af2a9ed1de09609ca770",
+ "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770",
"shasum": ""
},
"require": {
- "php": ">=7.3",
- "sebastian/diff": "^4.0",
- "sebastian/exporter": "^4.0"
+ "php": ">=7.1",
+ "sebastian/diff": "^3.0",
+ "sebastian/exporter": "^3.1"
},
"require-dev": {
- "phpunit/phpunit": "^9.3"
+ "phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "3.0-dev"
}
},
"autoload": {
@@ -3390,64 +3280,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
- "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8"
- },
- "funding": [
- {
- "url": "https://github.com/sebastianbergmann",
- "type": "github"
- }
- ],
- "time": "2022-09-14T12:41:17+00:00"
- },
- {
- "name": "sebastian/complexity",
- "version": "2.0.3",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/complexity.git",
- "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a",
- "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a",
- "shasum": ""
- },
- "require": {
- "nikic/php-parser": "^4.18 || ^5.0",
- "php": ">=7.3"
- },
- "require-dev": {
- "phpunit/phpunit": "^9.3"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "Library for calculating the complexity of PHP code units",
- "homepage": "https://github.com/sebastianbergmann/complexity",
- "support": {
- "issues": "https://github.com/sebastianbergmann/complexity/issues",
- "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3"
+ "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.5"
},
"funding": [
{
@@ -3455,33 +3288,33 @@
"type": "github"
}
],
- "time": "2023-12-22T06:19:30+00:00"
+ "time": "2022-09-14T12:31:48+00:00"
},
{
"name": "sebastian/diff",
- "version": "4.0.6",
+ "version": "3.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
- "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc"
+ "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc",
- "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/98ff311ca519c3aa73ccd3de053bdb377171d7b6",
+ "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6",
"shasum": ""
},
"require": {
- "php": ">=7.3"
+ "php": ">=7.1"
},
"require-dev": {
- "phpunit/phpunit": "^9.3",
- "symfony/process": "^4.2 || ^5"
+ "phpunit/phpunit": "^7.5 || ^8.0",
+ "symfony/process": "^2 || ^3.3 || ^4"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "3.0-dev"
}
},
"autoload": {
@@ -3513,7 +3346,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
- "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6"
+ "source": "https://github.com/sebastianbergmann/diff/tree/3.0.6"
},
"funding": [
{
@@ -3521,27 +3354,27 @@
"type": "github"
}
],
- "time": "2024-03-02T06:30:58+00:00"
+ "time": "2024-03-02T06:16:36+00:00"
},
{
"name": "sebastian/environment",
- "version": "5.1.5",
+ "version": "4.2.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed"
+ "reference": "56932f6049a0482853056ffd617c91ffcc754205"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
- "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/56932f6049a0482853056ffd617c91ffcc754205",
+ "reference": "56932f6049a0482853056ffd617c91ffcc754205",
"shasum": ""
},
"require": {
- "php": ">=7.3"
+ "php": ">=7.1"
},
"require-dev": {
- "phpunit/phpunit": "^9.3"
+ "phpunit/phpunit": "^7.5"
},
"suggest": {
"ext-posix": "*"
@@ -3549,7 +3382,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "5.1-dev"
+ "dev-master": "4.2-dev"
}
},
"autoload": {
@@ -3576,7 +3409,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
- "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5"
+ "source": "https://github.com/sebastianbergmann/environment/tree/4.2.5"
},
"funding": [
{
@@ -3584,34 +3417,34 @@
"type": "github"
}
],
- "time": "2023-02-03T06:03:51+00:00"
+ "time": "2024-03-01T13:49:59+00:00"
},
{
"name": "sebastian/exporter",
- "version": "4.0.6",
+ "version": "3.1.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72"
+ "reference": "1939bc8fd1d39adcfa88c5b35335910869214c56"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72",
- "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/1939bc8fd1d39adcfa88c5b35335910869214c56",
+ "reference": "1939bc8fd1d39adcfa88c5b35335910869214c56",
"shasum": ""
},
"require": {
- "php": ">=7.3",
- "sebastian/recursion-context": "^4.0"
+ "php": ">=7.2",
+ "sebastian/recursion-context": "^3.0"
},
"require-dev": {
"ext-mbstring": "*",
- "phpunit/phpunit": "^9.3"
+ "phpunit/phpunit": "^8.5"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "3.1.x-dev"
}
},
"autoload": {
@@ -3646,14 +3479,14 @@
}
],
"description": "Provides the functionality to export PHP variables for visualization",
- "homepage": "https://www.github.com/sebastianbergmann/exporter",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
- "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6"
+ "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.6"
},
"funding": [
{
@@ -3661,30 +3494,30 @@
"type": "github"
}
],
- "time": "2024-03-02T06:33:00+00:00"
+ "time": "2024-03-02T06:21:38+00:00"
},
{
"name": "sebastian/global-state",
- "version": "5.0.7",
+ "version": "3.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
- "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9"
+ "reference": "91c7c47047a971f02de57ed6f040087ef110c5d9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9",
- "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/91c7c47047a971f02de57ed6f040087ef110c5d9",
+ "reference": "91c7c47047a971f02de57ed6f040087ef110c5d9",
"shasum": ""
},
"require": {
- "php": ">=7.3",
- "sebastian/object-reflector": "^2.0",
- "sebastian/recursion-context": "^4.0"
+ "php": ">=7.2",
+ "sebastian/object-reflector": "^1.1.1",
+ "sebastian/recursion-context": "^3.0"
},
"require-dev": {
"ext-dom": "*",
- "phpunit/phpunit": "^9.3"
+ "phpunit/phpunit": "^8.0"
},
"suggest": {
"ext-uopz": "*"
@@ -3692,7 +3525,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "5.0-dev"
+ "dev-master": "3.0-dev"
}
},
"autoload": {
@@ -3717,7 +3550,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
- "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7"
+ "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.5"
},
"funding": [
{
@@ -3725,91 +3558,34 @@
"type": "github"
}
],
- "time": "2024-03-02T06:35:11+00:00"
- },
- {
- "name": "sebastian/lines-of-code",
- "version": "1.0.4",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/lines-of-code.git",
- "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5",
- "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5",
- "shasum": ""
- },
- "require": {
- "nikic/php-parser": "^4.18 || ^5.0",
- "php": ">=7.3"
- },
- "require-dev": {
- "phpunit/phpunit": "^9.3"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "Library for counting the lines of code in PHP source code",
- "homepage": "https://github.com/sebastianbergmann/lines-of-code",
- "support": {
- "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
- "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4"
- },
- "funding": [
- {
- "url": "https://github.com/sebastianbergmann",
- "type": "github"
- }
- ],
- "time": "2023-12-22T06:20:34+00:00"
+ "time": "2024-03-02T06:13:16+00:00"
},
{
"name": "sebastian/object-enumerator",
- "version": "4.0.4",
+ "version": "3.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/object-enumerator.git",
- "reference": "5c9eeac41b290a3712d88851518825ad78f45c71"
+ "reference": "ac5b293dba925751b808e02923399fb44ff0d541"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71",
- "reference": "5c9eeac41b290a3712d88851518825ad78f45c71",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/ac5b293dba925751b808e02923399fb44ff0d541",
+ "reference": "ac5b293dba925751b808e02923399fb44ff0d541",
"shasum": ""
},
"require": {
- "php": ">=7.3",
- "sebastian/object-reflector": "^2.0",
- "sebastian/recursion-context": "^4.0"
+ "php": ">=7.0",
+ "sebastian/object-reflector": "^1.1.1",
+ "sebastian/recursion-context": "^3.0"
},
"require-dev": {
- "phpunit/phpunit": "^9.3"
+ "phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "3.0.x-dev"
}
},
"autoload": {
@@ -3831,7 +3607,7 @@
"homepage": "https://github.com/sebastianbergmann/object-enumerator/",
"support": {
"issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
- "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4"
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.5"
},
"funding": [
{
@@ -3839,32 +3615,32 @@
"type": "github"
}
],
- "time": "2020-10-26T13:12:34+00:00"
+ "time": "2024-03-01T13:54:02+00:00"
},
{
"name": "sebastian/object-reflector",
- "version": "2.0.4",
+ "version": "1.1.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/object-reflector.git",
- "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7"
+ "reference": "1d439c229e61f244ff1f211e5c99737f90c67def"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
- "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/1d439c229e61f244ff1f211e5c99737f90c67def",
+ "reference": "1d439c229e61f244ff1f211e5c99737f90c67def",
"shasum": ""
},
"require": {
- "php": ">=7.3"
+ "php": ">=7.0"
},
"require-dev": {
- "phpunit/phpunit": "^9.3"
+ "phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.0-dev"
+ "dev-master": "1.1-dev"
}
},
"autoload": {
@@ -3886,7 +3662,7 @@
"homepage": "https://github.com/sebastianbergmann/object-reflector/",
"support": {
"issues": "https://github.com/sebastianbergmann/object-reflector/issues",
- "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4"
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.3"
},
"funding": [
{
@@ -3894,32 +3670,32 @@
"type": "github"
}
],
- "time": "2020-10-26T13:14:26+00:00"
+ "time": "2024-03-01T13:56:04+00:00"
},
{
"name": "sebastian/recursion-context",
- "version": "4.0.5",
+ "version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
- "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1"
+ "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
- "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/9bfd3c6f1f08c026f542032dfb42813544f7d64c",
+ "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c",
"shasum": ""
},
"require": {
- "php": ">=7.3"
+ "php": ">=7.0"
},
"require-dev": {
- "phpunit/phpunit": "^9.3"
+ "phpunit/phpunit": "^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.0-dev"
+ "dev-master": "3.0.x-dev"
}
},
"autoload": {
@@ -3946,10 +3722,10 @@
}
],
"description": "Provides functionality to recursively process PHP variables",
- "homepage": "https://github.com/sebastianbergmann/recursion-context",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
- "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5"
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.2"
},
"funding": [
{
@@ -3957,32 +3733,29 @@
"type": "github"
}
],
- "time": "2023-02-03T06:07:39+00:00"
+ "time": "2024-03-01T14:07:30+00:00"
},
{
"name": "sebastian/resource-operations",
- "version": "3.0.4",
+ "version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/resource-operations.git",
- "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e"
+ "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e",
- "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/72a7f7674d053d548003b16ff5a106e7e0e06eee",
+ "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee",
"shasum": ""
},
"require": {
- "php": ">=7.3"
- },
- "require-dev": {
- "phpunit/phpunit": "^9.0"
+ "php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-main": "3.0-dev"
+ "dev-master": "2.0-dev"
}
},
"autoload": {
@@ -4003,7 +3776,7 @@
"description": "Provides a list of PHP built-in functions that operate on resources",
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"support": {
- "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4"
+ "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.3"
},
"funding": [
{
@@ -4011,32 +3784,32 @@
"type": "github"
}
],
- "time": "2024-03-14T16:00:52+00:00"
+ "time": "2024-03-01T13:59:09+00:00"
},
{
"name": "sebastian/type",
- "version": "3.2.1",
+ "version": "1.1.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
- "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7"
+ "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
- "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/18f071c3a29892b037d35e6b20ddf3ea39b42874",
+ "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874",
"shasum": ""
},
"require": {
- "php": ">=7.3"
+ "php": ">=7.2"
},
"require-dev": {
- "phpunit/phpunit": "^9.5"
+ "phpunit/phpunit": "^8.2"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.2-dev"
+ "dev-master": "1.1-dev"
}
},
"autoload": {
@@ -4059,7 +3832,7 @@
"homepage": "https://github.com/sebastianbergmann/type",
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
- "source": "https://github.com/sebastianbergmann/type/tree/3.2.1"
+ "source": "https://github.com/sebastianbergmann/type/tree/1.1.5"
},
"funding": [
{
@@ -4067,29 +3840,29 @@
"type": "github"
}
],
- "time": "2023-02-03T06:13:03+00:00"
+ "time": "2024-03-01T14:04:07+00:00"
},
{
"name": "sebastian/version",
- "version": "3.0.2",
+ "version": "2.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/version.git",
- "reference": "c6c1022351a901512170118436c764e473f6de8c"
+ "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c",
- "reference": "c6c1022351a901512170118436c764e473f6de8c",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
+ "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
"shasum": ""
},
"require": {
- "php": ">=7.3"
+ "php": ">=5.6"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.0-dev"
+ "dev-master": "2.0.x-dev"
}
},
"autoload": {
@@ -4112,15 +3885,9 @@
"homepage": "https://github.com/sebastianbergmann/version",
"support": {
"issues": "https://github.com/sebastianbergmann/version/issues",
- "source": "https://github.com/sebastianbergmann/version/tree/3.0.2"
+ "source": "https://github.com/sebastianbergmann/version/tree/master"
},
- "funding": [
- {
- "url": "https://github.com/sebastianbergmann",
- "type": "github"
- }
- ],
- "time": "2020-09-28T06:39:44+00:00"
+ "time": "2016-10-03T07:35:21+00:00"
},
{
"name": "theseer/tokenizer",
diff --git a/solid/css/settings-admin.css b/solid/css/settings-admin.css
index 1facf521..9ca74f0a 100644
--- a/solid/css/settings-admin.css
+++ b/solid/css/settings-admin.css
@@ -1,4 +1,4 @@
-#solid-admin label {
+#solid-admin label.narrow {
width: 160px;
vertical-align: top;
display: block;
@@ -8,4 +8,8 @@
height: 240px;
font-size: 12px;
font-family: monospace;
+}
+#solid-admin input.textaligned {
+ height: 1rem;
+ min-height: unset;
}
\ No newline at end of file
diff --git a/solid/js/settings-admin.js b/solid/js/settings-admin.js
index 8f63de10..9aac545f 100644
--- a/solid/js/settings-admin.js
+++ b/solid/js/settings-admin.js
@@ -1,4 +1,8 @@
-$(document).ready(function() {
+$(document).ready(function () {
+ $('#solid-enable-user-subdomains').change(function (el) {
+ OCP.AppConfig.setValue('solid', 'userSubDomainsEnabled', this.checked ? true : false)
+ })
+
$('#solid-private-key').change(function(el) {
OCP.AppConfig.setValue('solid', 'privateKey', this.value);
});
diff --git a/solid/js/vendor/simplyedit/simply-edit.js b/solid/js/vendor/simplyedit/simply-edit.js
index e140b304..27eb96a9 100644
--- a/solid/js/vendor/simplyedit/simply-edit.js
+++ b/solid/js/vendor/simplyedit/simply-edit.js
@@ -17,9 +17,6 @@
if (!scriptEl) {
scriptEl = document.querySelector("[data-api-key]");
}
- if (!scriptEl) {
- scriptEl = document.querySelector("[src*='simply-edit.js']");
- }
return scriptEl;
};
@@ -37,7 +34,7 @@
var scriptURL = document.createElement('a');
scriptURL.href = url;
scriptURL.pathname = scriptURL.pathname.replace('simply-edit.js', '').replace(/\/js\/$/, '/');
- if (apiKey !== "" && apiKey !== "muze" && apiKey !== "github") {
+ if (apiKey !== "") {
scriptURL.pathname = scriptURL.pathname + apiKey + "/";
}
return scriptURL.href;
@@ -103,7 +100,10 @@
var dataFields;
if (target.nodeType == document.ELEMENT_NODE && target.getAttribute("data-simply-field")) {
dataFields = [target];
- if (target.getAttribute("data-simply-content") === 'fixed') { // special case - if the target field has content fixed, we need to handle its children as well.
+ if (
+ (target.getAttribute("data-simply-content") === 'fixed') ||
+ (target.getAttribute("data-simply-content") === 'attributes')
+ ) { // special case - if the target field has content fixed or attributes, we need to handle its children as well.
var extraFields = target.querySelectorAll("[data-simply-field]");
for (var x=0; x -1);
+ if (isSub) {
+ continue;
+ }
+ editor.list.init(dataLists[i], listDataItem, useDataBinding);
}
+
if (clone.nodeType == document.ELEMENT_NODE && clone.getAttribute("data-simply-list")) {
editor.list.init(clone, listDataItem, useDataBinding);
}
@@ -825,11 +860,37 @@
list.dataBinding.pauseListeners(list);
}
+ var transformer = list.getAttribute('data-simply-transformer');
+ if (transformer) {
+ if (editor.transformers[transformer] && (typeof editor.transformers[transformer].render === "function")) {
+ try {
+ listData = editor.transformers[transformer].render.call(list, listData);
+ } catch(e) {
+ console.log("Error thrown in transformer " + transformer);
+ console.log(e);
+ }
+ } else {
+ console.log("Warning: transformer " + transformer + " is not defined");
+ }
+ }
+
+ if (list.previousValue == JSON.stringify(listData)) {
+ if (list.dataBinding) {
+ list.dataBinding.resumeListeners(list);
+ }
+ return; // value is the same as the previous time we set it, just keep it;
+ }
+
+ list.previousValue = JSON.stringify(listData);
var previousStyle = list.getAttribute("style");
list.style.height = list.offsetHeight + "px"; // this will prevent the screen from bouncing and messing up the scroll offset.
editor.list.clear(list);
editor.list.append(list, listData);
- list.setAttribute("style", previousStyle);
+ if (previousStyle) {
+ list.setAttribute("style", previousStyle);
+ } else {
+ list.removeAttribute("style");
+ }
editor.list.emptyClass(list);
if (list.dataBinding) {
list.dataBinding.resumeListeners(list);
@@ -857,7 +918,7 @@
// Grr... android browser imports the nodes, except the contents of subtemplates. Find them and put them back where they belong.
var originalTemplates = template.content.querySelectorAll("template");
- var importedTemplates = clone.querySelectorAll("template");
+ var importedTemplates = clone.querySelectorAll("template:not([simply-component])");
for (i=0; i " + newparent);
- value._bindings_[subbinding].parentKey = newparent;
- if (value[subbinding] && value[subbinding].length) {
- for (var i=0; i [data-simply-list-item]");
+ //for (i=0; i [data-simply-list-item]");
+ for (i=0; i 5) {
- console.log("Warning: databinding resolve loop detected!");
+ };
+
+ this.handleEvent = function (event) {
+ var target = event.currentTarget;
+ var dataBinding = target.dataBinding;
+ var elementBinding = target.elementBinding;
+
+ if (typeof dataBinding === 'undefined') {
+ return;
+ }
+ if (dataBinding.paused) {
+ return;
+ }
+ if (target.dataBindingPaused) {
+ event.stopPropagation();
+ return;
+ }
+ if (dataBinding.mode === "list") {
+ if (event.relatedNode && (target != event.relatedNode)) {
+ return;
+ }
+ }
+
+ var i, data, items;
+
+ switch (event.type) {
+ case "change":
+ case "databinding:valuechanged":
+ // Allow the browser to fix what it thinks needs to be fixed (node to be removed, cleaned etc) before setting the new data;
+
+ // these are needed to keep the focus in an element while typing;
+ elementBinding.pauseListeners();
+ dataBinding.set(elementBinding.getter());
+ elementBinding.resumeListeners();
+
+ // these are needed to update after the browser is done doing its thing;
window.setTimeout(function() {
- binding.resolveCounter = 0;
- }, 300); // 300 is a guess; could be any other number. It needs to be long enough so that everyone can settle down before we start resolving again.
+ elementBinding.pauseListeners();
+ dataBinding.set(elementBinding.getter());
+ elementBinding.resumeListeners();
+ }, 1); // allow the rest of the mutation event to occur;
+ break;
+ }
+ elementBinding.fireEvent("domchanged");
+ };
+ this.fireEvent = function(event) {
+ self.dataBinding.fireEvent(self.element, event);
+ };
+ this.fireParent = function(event) {
+ self.dataBinding.fireEvent(self.element.parentNode, event);
+ };
+ this.isInDocument = function() {
+ if (document.contains && document.contains(this.element)) {
+ return true;
+ }
+ var parent = element.parentNode;
+ while (parent) {
+ if (parent === document) {
return true;
}
- return false;
- };
-
- var setElements = function() {
- if (binding.elementTimer) {
- window.clearTimeout(binding.elementTimer);
- }
- for (var i=0; i -1) {
- binding.removeListeners(element);
- binding.elements.splice(binding.elements.indexOf(element), 1);
- }
- };
-
- this.cleanupBindings = function() {
- if (binding.elements.length < 2) {
- return;
+ shadowValue._bindings_[i] = valueBindings[i];
}
-
- var inDocument = function(element) {
- if (document.contains && document.contains(element)) {
- return true;
+ }
+
+ if (typeof oldValue !== "undefined" && !isEqual(oldValue, shadowValue)) {
+ binding.config.resolve.call(binding, key, dereference(shadowValue), dereference(oldValue));
+ }
+ //if (typeof shadowValue === "object") {
+ // shadowValue = dereference(shadowValue);
+ //}
+ updateConvertedDataParent(shadowValue);
+ monitorChildData(shadowValue);
+ };
+
+ var updateConvertedDataParent = function(data) {
+ if (
+ binding.config.data._parentBindings_ &&
+ binding.config.data._parentBindings_[binding.key] &&
+ binding.config.data._parentBindings_[binding.key].config.data._simplyListEntryMapping
+ ) {
+ var listEntryMapping = binding.config.data._parentBindings_[binding.key].config.data._simplyListEntryMapping;
+ var convertedParent = binding.config.data._parentBindings_[binding.key].config.data._simplyConvertedParent;
+ var arrayPaths = binding.config.data._parentBindings_[binding.key].config.data[listEntryMapping]._parentBindings_[binding.key].parentKey.split("/");
+ var arrayIndex = arrayPaths.pop();
+ arrayIndex = arrayPaths.pop();
+ binding.config.data._parentBindings_[binding.key].config.data[binding.key] = data;
+ var parentData = convertedParent._parentBindings_[arrayIndex].config.data;
+ var parentKey = arrayPaths.pop();
+ parentData[parentKey][arrayIndex][binding.key] = data;
+ }
+ };
+
+ var monitorChildData = function(data) {
+ // Watch for changes in our child data, because these also need to register as changes in the databound data/elements;
+ // This allows the use of simple data structures (1 key deep) as databound values and still resolve changes on a specific entry;
+ var parentData = data;
+
+ if (typeof data === "object") {
+ var monitor = function(data, key) {
+ if (!data.hasOwnProperty("_parentBindings_")) {
+ var bindings = {};
+
+ Object.defineProperty(data, "_parentBindings_", {
+ get : function() {
+ return bindings;
+ },
+ set : function(value) {
+ bindings[key] = binding;
+ }
+ });
+ Object.defineProperty(data, "_parentData_", {
+ get : function() {
+ return parentData;
+ }
+ });
}
- var parent = element.parentNode;
- while (parent) {
- if (parent === document) {
- return true;
+ data._parentBindings_[key] = binding;
+
+ var myvalue = data[key];
+
+ var renumber = function(key, value, parentBinding) {
+ var oldparent, newparent;
+ if (value && value._bindings_) {
+ for (var subbinding in value._bindings_) {
+ oldparent = value._bindings_[subbinding].parentKey;
+ newparent = parentBinding.parentKey + parentBinding.key + "/" + key + "/";
+ // console.log(oldparent + " => " + newparent);
+ value._bindings_[subbinding].parentKey = newparent;
+ if (value[subbinding] && value[subbinding].length && (typeof value[subbinding] !== "string")) {
+ for (var i=0; i 5) {
+ console.log("Warning: databinding resolve loop detected!");
+ window.setTimeout(function() {
+ binding.resolveCounter = 0;
+ }, 300); // 300 is a guess; could be any other number. It needs to be long enough so that everyone can settle down before we start resolving again.
+ return true;
}
+ return false;
};
-
- dataBinding.prototype.addListeners = function(element) {
- if (element.dataBinding) {
- element.dataBinding.removeListeners(element);
+
+ var setElements = function() {
+ if (binding.elementTimer) {
+ window.clearTimeout(binding.elementTimer);
}
- if (typeof element.mutationObserver === "undefined") {
- if (typeof MutationObserver === "function") {
- element.mutationObserver = new MutationObserver(this.handleMutation);
+ for (var i=0; i [data-simply-list-item]");
- for (i=0; i [data-simply-list-item]");
- for (i=0; i -1) {
+ element.removeListeners();
+ binding.elements.splice(binding.elements.indexOf(element), 1);
}
- self.fireEvent(target, "domchanged");
};
- // Housekeeping, remove references to deleted nodes
- document.addEventListener("DOMNodeRemoved", function(evt) {
- var target = evt.target;
- if (target.nodeType != document.ELEMENT_NODE) { // We don't care about removed text nodes;
+ this.cleanupBindings = function() {
+ if (binding.elements.length < 2) {
return;
}
- if (!target.dataBinding) { // nor any element that doesn't have a databinding;
- return;
+
+ binding.elements.forEach(function(element) {
+ if (!element.isInDocument()) {
+ element.markedForRemoval = true;
+ } else {
+ element.markedForRemoval = false;
+ }
+ });
+
+ if (binding.cleanupTimer) {
+ clearTimeout(binding.cleanupTimer);
}
- window.setTimeout(function() { // chrome sometimes 'helpfully' removes the element and then inserts it back, probably as a rendering optimalization. We're fine cleaning up in a bit, if still needed.
- if (!target.parentNode && target.dataBinding) {
- target.dataBinding.unbind(target);
- delete target.dataBinding;
+
+ binding.cleanupTimer = window.setTimeout(function() {
+ binding.elements.filter(function(element) {
+ if (element.markedForRemoval && !element.isInDocument()) {
+ element.dataBinding.unbind(element);
+ return false;
+ }
+ element.markedForRemoval = false;
+ return true;
+ });
+ }, 1000); // If after 1 second the element is still not in the dom, remove the binding;
+ };
+
+ initBindings(data, key);
+ // Call the custom init function, if it is there;
+ if (typeof binding.config.init === "function") {
+ binding.config.init.call(binding);
+ }
+
+ if (binding.mode == "list") {
+ document.addEventListener("databind:resolved", function() {
+ if (!binding.skipOldValueUpdate) {
+ oldValue = dereference(binding.get());
}
- }, 1000);
- });
-
- // polyfill to add :scope selector for IE
- (function() {
- if (!HTMLElement.prototype.querySelectorAll) {
- throw new Error('rootedQuerySelectorAll: This polyfill can only be used with browsers that support querySelectorAll');
- }
-
- // A temporary element to query against for elements not currently in the DOM
- // We'll also use this element to test for :scope support
- var container = document.createElement('div');
-
- // Check if the browser supports :scope
- try {
- // Browser supports :scope, do nothing
- container.querySelectorAll(':scope *');
- }
- catch (e) {
- // Match usage of scope
- var scopeRE = /\s*:scope/gi;
-
- // Overrides
- function overrideNodeMethod(prototype, methodName) {
- // Store the old method for use later
- var oldMethod = prototype[methodName];
-
- // Override the method
- prototype[methodName] = function(query) {
- var nodeList,
- gaveId = false,
- gaveContainer = false;
-
- if (query.match(scopeRE)) {
- if (!this.parentNode) {
- // Add to temporary container
- container.appendChild(this);
- gaveContainer = true;
- }
-
- parentNode = this.parentNode;
-
- if (!this.id) {
- // Give temporary ID
- this.id = 'rootedQuerySelector_id_'+(new Date()).getTime();
- gaveId = true;
- }
-
- // Remove :scope
- query = query.replace(scopeRE, '#' + this.id + " ");
-
- // Find elements against parent node
- // nodeList = oldMethod.call(parentNode, '#'+this.id+' '+query);
- nodeList = parentNode[methodName](query);
- // Reset the ID
- if (gaveId) {
- this.id = '';
- }
-
- // Remove from temporary container
- if (gaveContainer) {
- container.removeChild(this);
- }
-
- return nodeList;
- }
- else {
- // No immediate child selector used
- return oldMethod.call(this, query);
- }
- };
- }
-
- // Browser doesn't support :scope, add polyfill
- overrideNodeMethod(HTMLElement.prototype, 'querySelector');
- overrideNodeMethod(HTMLElement.prototype, 'querySelectorAll');
- }
- }());
-
- editor.init({
- endpoint : document.querySelector("[data-simply-endpoint]") ? document.querySelector("[data-simply-endpoint]").getAttribute("data-simply-endpoint") : null,
- toolbars : defaultToolbars,
- profile : 'live'
+ });
+ }
+};
+
+dataBinding.prototype.resumeListeners = function(element) {
+ element.dataBindingPaused--;
+ if (element.dataBindingPaused < 0) {
+ console.log("Warning: resume called of non-paused databinding");
+ element.dataBindingPaused = 0;
+ }
+ if (element.dataBindingPaused === 0) {
+ if (element.mutationObserver) {
+ element.mutationObserver.observe(element, element.mutationObserverConfig);
+ element.mutationObserver.status = "observing";
+ } else {
+ console.log("Warning: no mutation observer found");
+ }
+ }
+};
+dataBinding.prototype.pauseListeners = function(element) {
+ element.dataBindingPaused++;
+ if (element.mutationObserver) {
+ element.mutationObserver.status = "disconnected";
+ element.mutationObserver.disconnect();
+ }
+};
+
+// Housekeeping, remove references to deleted nodes
+var removalObserver = new MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ if (mutation.type == "childList") {
+ mutation.removedNodes.forEach(function(target) {
+ if (target.nodeType != document.ELEMENT_NODE) { // We don't care about removed text nodes;
+ return;
+ }
+ if (!target.dataBinding) { // nor any element that doesn't have a databinding;
+ return;
+ }
+ window.setTimeout(function() { // chrome sometimes 'helpfully' removes the element and then inserts it back, probably as a rendering optimalization. We're fine cleaning up in a bit, if still needed.
+ if (!target.parentNode && target.dataBinding && target.elementBinding) {
+ target.dataBinding.unbind(target.elementBinding);
+ // if (target.dataBinding.mode == "field") {
+ // target.dataBinding.set();
+ // }
+ delete target.dataBinding;
+ }
+ }, 400);
+ });
+ }
});
+});
+
+removalObserver.observe(document.body, {
+ "childList" : true,
+ "subtree" : true
+});
+
+// polyfill to add :scope selector for IE
+(function() {
+ if (!HTMLElement.prototype.querySelectorAll) {
+ throw new Error('rootedQuerySelectorAll: This polyfill can only be used with browsers that support querySelectorAll');
+ }
+
+ // A temporary element to query against for elements not currently in the DOM
+ // We'll also use this element to test for :scope support
+ var container = document.createElement('div');
+
+ // Check if the browser supports :scope
+ try {
+ // Browser supports :scope, do nothing
+ container.querySelectorAll(':scope *');
+ }
+ catch (e) {
+ // Match usage of scope
+ var scopeRE = /\s*:scope/gi;
+
+ // Overrides
+ function overrideNodeMethod(prototype, methodName) {
+ // Store the old method for use later
+ var oldMethod = prototype[methodName];
+
+ // Override the method
+ prototype[methodName] = function(query) {
+ var nodeList,
+ gaveId = false,
+ gaveContainer = false;
+
+ if (query.match(scopeRE)) {
+ if (!this.parentNode) {
+ // Add to temporary container
+ container.appendChild(this);
+ gaveContainer = true;
+ }
+
+ parentNode = this.parentNode;
+
+ if (!this.id) {
+ // Give temporary ID
+ this.id = 'rootedQuerySelector_id_'+(new Date()).getTime();
+ gaveId = true;
+ }
+
+ // Remove :scope
+ query = query.replace(scopeRE, '#' + this.id + " ");
+
+ // Find elements against parent node
+ // nodeList = oldMethod.call(parentNode, '#'+this.id+' '+query);
+ nodeList = parentNode[methodName](query);
+ // Reset the ID
+ if (gaveId) {
+ this.id = '';
+ }
+
+ // Remove from temporary container
+ if (gaveContainer) {
+ container.removeChild(this);
+ }
+
+ return nodeList;
+ }
+ else {
+ // No immediate child selector used
+ return oldMethod.call(this, query);
+ }
+ };
+ }
+
+ // Browser doesn't support :scope, add polyfill
+ overrideNodeMethod(HTMLElement.prototype, 'querySelector');
+ overrideNodeMethod(HTMLElement.prototype, 'querySelectorAll');
+ }
}());
diff --git a/solid/js/vendor/simplyedit/simply.everything.js b/solid/js/vendor/simplyedit/simply.everything.js
index ed6561a5..57e66774 100644
--- a/solid/js/vendor/simplyedit/simply.everything.js
+++ b/solid/js/vendor/simplyedit/simply.everything.js
@@ -1,227 +1,364 @@
-this.simply = (function(simply, global) {
-
- simply.view = function(app, view) {
-
- app.view = view || {};
-
- var load = function() {
- var data = app.view;
- var path = editor.data.getDataPath(app.container);
- app.view = editor.currentData[path];
- Object.keys(data).forEach(function(key) {
- app.view[key] = data[key];
- });
- };
-
- if (global.editor && editor.currentData) {
- load();
- } else {
- document.addEventListener('simply-content-loaded', function() {
- load();
- });
- }
-
- return app.view;
- };
+/**
+ * simply.observe
+ * This component lets you observe changes in a json compatible data structure
+ * It doesn't support linking the same object multiple times
+ * It doesn't register deletion of properties using the delete keyword, assign
+ * null to the property instead.
+ * It doesn't register addition of new properties.
+ * It doesn't register directly assigning new entries in an array on a previously
+ * non-existant index.
+ *
+ * usage:
+ *
+ * (function) simply.observe( (object) model, (string) path, (function) callback)
+ *
+ * var model = { foo: { bar: 'baz' } };
+ * var removeObserver = simply.observe(model, 'foo.bar', function(value, sourcePath) {
+ * console.log(sourcePath+': '+value);
+ * };
+ *
+ * The function returns a function that removes the observer when called.
+ *
+ * The component can observe in place changes in arrays, either by changing
+ * an item in a specific index, by calling methods on the array that change
+ * the array in place or by reassigning the array with a new value.
+ *
+ * The sourcePath contains the exact entry that was changed, the value is the
+ * value for the path passed to simply.observe.
+ * If an array method was called that changes the array in place, the sourcePath
+ * also contains that method and its arguments JSON serialized.
+ *
+ * sourcePath parts are always seperated with '.', even for array indexes.
+ * so if foo = [ 'bar' ], the path to 'bar' would be 'foo.0'
+ */
- return simply;
-})(this.simply || {}, this);
-this.simply = (function(simply, global) {
+ /*
+ FIXME: child properties added after initial observe() call aren't added to the
+ childListeners. onMissingChildren can't then find them.
+ TODO: onMissingChildren must loop through all fields to get only the direct child
+properties for a given parent, keep seperate index for this?
+ */
- var routeInfo = [];
+(function (global) {
+ 'use strict';
- function parseRoutes(routes) {
- var paths = Object.keys(routes);
- var matchParams = /:(\w+|\*)/g;
- var matches, params, path;
- for (var i=0; ipath.length;
+ });
+ if (!allChildren.length) {
+ return;
+ }
+ var object = getByPath(model, path);
+ var keysSeen = {};
+ allChildren.forEach(function(childPath) {
+ var key = head(childPath.substr(path.length+1));
+ if (typeof object[key] == 'undefined') {
+ if (!keysSeen[key]) {
+ callback(object, key, path+'.'+key);
+ keysSeen[key] = true;
+ }
+ } else {
+ onMissingChildren(model, path+'.'+key, callback);
+ }
+ });
+ }
+
+ function addChangeListener(model, path, callback) {
+ if (!changeListeners.has(model)) {
+ changeListeners.set(model, {});
+ }
+ if (!changeListeners.get(model)[path]) {
+ changeListeners.get(model)[path] = [];
+ }
+ changeListeners.get(model)[path].push(callback);
+
+ if (!parentListeners.has(model)) {
+ parentListeners.set(model, {});
+ }
+ var parentPath = parent(path);
+ onParents(model, parentPath, function(parentOb, key, currPath) {
+ if (!parentListeners.get(model)[currPath]) {
+ parentListeners.get(model)[currPath] = [];
+ }
+ parentListeners.get(model)[currPath].push(path);
+ });
+
+ if (!childListeners.has(model)) {
+ childListeners.set(model, {});
+ }
+ onChildren(model, path, function(childOb, key, currPath) {
+ if (!childListeners.get(model)[currPath]) {
+ childListeners.get(model)[currPath] = [];
+ }
+ childListeners.get(model)[currPath].push(path);
+ });
+ }
+
+ function removeChangeListener(model, path, callback) {
+ if (!changeListeners.has(model)) {
+ return;
+ }
+ if (changeListeners.get(model)[path]) {
+ changeListeners.get(model)[path] = changeListeners.get(model)[path].filter(function(f) {
+ return f != callback;
+ });
+ }
+ }
+
+ function pauseObservers() {
+ observersPaused++;
+ }
+
+ function resumeObservers() {
+ observersPaused--;
+ }
+
+ function attach(model, path, options) {
+
+ var attachArray = function(object, path) {
+ var desc = Object.getOwnPropertyDescriptor(object, 'push');
+ if (!desc || desc.configurable) {
+ for (var f of ['push','pop','reverse','shift','sort','splice','unshift','copyWithin']) {
+ (function(f) {
+ try {
+ Object.defineProperty(object, f, {
+ value: function() {
+ pauseObservers();
+ var result = Array.prototype[f].apply(this, arguments);
+ attach(model, path);
+ var args = [].slice.call(arguments).map(function(arg) {
+ return JSON.stringify(arg);
+ });
+ resumeObservers();
+ signalChange(model, path, this, path+'.'+f+'('+args.join(',')+')');
+ return result;
+ },
+ readable: false,
+ enumerable: false,
+ configurable: false
+ });
+ } catch(e) {
+ console.error('simply.observer: Error: Couldn\'t redefine array method '+f+' on '+path, e);
+ }
+ }(f));
+ }
+ for (var i=0, l=object.length; ipath.length;
- });
- if (!allChildren.length) {
- return;
- }
- var object = getByPath(model, path);
- var keysSeen = {};
- allChildren.forEach(function(childPath) {
- var key = head(childPath.substr(path.length+1));
- if (typeof object[key] == 'undefined') {
- if (!keysSeen[key]) {
- callback(object, key, path+'.'+key);
- keysSeen[key] = true;
- }
- } else {
- onMissingChildren(model, path+'.'+key, callback);
+ },
+ init: function(params) {
+ if (params.root) {
+ options.root = params.root;
}
- });
- }
-
- function addChangeListener(model, path, callback) {
- if (!changeListeners.has(model)) {
- changeListeners.set(model, {});
- }
- if (!changeListeners.get(model)[path]) {
- changeListeners.get(model)[path] = [];
}
- changeListeners.get(model)[path].push(callback);
-
- if (!parentListeners.has(model)) {
- parentListeners.set(model, {});
- }
- var parentPath = parent(path);
- onParents(model, parentPath, function(parentOb, key, currPath) {
- if (!parentListeners.get(model)[currPath]) {
- parentListeners.get(model)[currPath] = [];
- }
- parentListeners.get(model)[currPath].push(path);
- });
+ };
- if (!childListeners.has(model)) {
- childListeners.set(model, {});
+ if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
+ module.exports = route;
+ } else {
+ if (!global.simply) {
+ global.simply = {};
}
- onChildren(model, path, function(childOb, key, currPath) {
- if (!childListeners.get(model)[currPath]) {
- childListeners.get(model)[currPath] = [];
- }
- childListeners.get(model)[currPath].push(path);
- });
+ global.simply.route = route;
}
+})(this);
+(function(global) {
+ 'use strict';
- function removeChangeListener(model, path, callback) {
- if (!changeListeners.has(model)) {
- return;
- }
- if (changeListeners.get(model)[path]) {
- changeListeners.get(model)[path] = changeListeners.get(model)[path].filter(function(f) {
- return f != callback;
- });
- }
- }
+ var listeners = {};
- function pauseObservers() {
- observersPaused++;
- }
-
- function resumeObservers() {
- observersPaused--;
- }
-
- function attach(model, path, options) {
-
- var attachArray = function(object, path) {
- var desc = Object.getOwnPropertyDescriptor(object, 'push');
- if (!desc || desc.configurable) {
- for (var f of ['push','pop','reverse','shift','sort','splice','unshift','copyWithin']) {
- (function(f) {
- try {
- Object.defineProperty(object, f, {
- value: function() {
- pauseObservers();
- var result = Array.prototype[f].apply(this, arguments);
- attach(model, path);
- var args = [].slice.call(arguments).map(function(arg) {
- return JSON.stringify(arg);
- });
- resumeObservers();
- signalChange(model, path, this, path+'.'+f+'('+args.join(',')+')');
- return result;
- },
- readable: false,
- enumerable: false,
- configurable: false
- });
- } catch(e) {
- console.error('simply.observer: Error: Couldn\'t redefine array method '+f+' on '+path, e);
- }
- }(f));
- }
- for (var i=0, l=object.length; i=0) {
+ knownCollections[name].splice(index, 1);
+ }
+ }
+ },
+ update: function(element, value) {
+ element.value = value;
+ element.dispatchEvent(new Event('change', {
+ bubbles: true,
+ cancelable: true
+ }));
+ }
};
- var handleChanges = throttle(function() {
- runWhenIdle(function() {
- var links = document.querySelectorAll('link[rel="simply-include"],link[rel="simply-include-once"]');
- if (links.length) {
- includeLinks(links);
+ function findCollection(el) {
+ while (el && !el.dataset.simplyCollection) {
+ el = el.parentElement;
+ }
+ return el;
+ }
+
+ global.addEventListener('change', function(evt) {
+ var root = null;
+ var name = '';
+ if (evt.target.dataset.simplyElement) {
+ root = findCollection(evt.target);
+ if (root && root.dataset) {
+ name = root.dataset.simplyCollection;
}
- });
- });
-
- var observe = function() {
- observer = new MutationObserver(handleChanges);
- observer.observe(document, {
- subtree: true,
- childList: true,
- });
- };
-
- observe();
+ }
+ if (name && knownCollections[name]) {
+ var inputs = root.querySelectorAll('[data-simply-element]');
+ var elements = [].reduce.call(inputs, function(elements, input) {
+ elements[input.dataset.simplyElement] = input;
+ return elements;
+ }, {});
+ for (var i=knownCollections[name].length-1; i>=0; i--) {
+ var result = knownCollections[name][i].call(evt.target.form, elements);
+ if (result === false) {
+ break;
+ }
+ }
+ }
+ }, true);
- return simply;
+ if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
+ module.exports = collect;
+ } else {
+ if (!global.simply) {
+ global.simply = {};
+ }
+ global.simply.collect = collect;
+ }
-})(this.simply || {}, this);
-this.simply = (function(simply, global) {
+})(this);
+(function(global) {
+ 'use strict';
var defaultCommands = {
'simply-hide': function(el, value) {
@@ -957,6 +951,16 @@ this.simply = (function(simply, global) {
{
match: 'input,select,textarea',
get: function(el) {
+ if (el.tagName==='SELECT' && el.multiple) {
+ var values = [], opt;
+ for (var i=0,l=el.options.length;i {
+ if (e.isComposing || e.keyCode === 229) {
+ return;
}
- if (knownCollections[name].indexOf(callback) == -1) {
- knownCollections[name].push(callback);
+ if (e.defaultPrevented) {
+ return;
}
- },
- removeListener: function(name, callback) {
- if (knownCollections[name]) {
- var index = knownCollections[name].indexOf(callback);
- if (index>=0) {
- knownCollections[name].splice(index, 1);
- }
+ if (!e.target) {
+ return;
}
- },
- update: function(element, value) {
- element.value = value;
- element.dispatchEvent(new Event('change', {
- bubbles: true,
- cancelable: true
- }));
- }
- };
- function findCollection(el) {
- while (el && !el.dataset.simplyCollection) {
- el = el.parentElement;
+ let selectedKeyboard = 'default';
+ if (e.target.closest('[data-simply-keyboard]')) {
+ selectedKeyboard = e.target.closest('[data-simply-keyboard]').dataset.simplyKeyboard;
+ }
+ let key = '';
+ if (e.ctrlKey && e.keyCode!=17) {
+ key+='Control+';
+ }
+ if (e.metaKey && e.keyCode!=224) {
+ key+='Meta+';
+ }
+ if (e.altKey && e.keyCode!=18) {
+ key+='Alt+';
+ }
+ if (e.shiftKey && e.keyCode!=16) {
+ key+='Shift+';
+ }
+ key+=e.key;
+
+ if (keys[selectedKeyboard] && keys[selectedKeyboard][key]) {
+ let keyboard = keys[selectedKeyboard]
+ keyboard.app = app;
+ keyboard[key].call(keyboard,e);
+ }
+ });
+
+ return keys;
+ }
+
+
+ if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
+ module.exports = keyboard;
+ } else {
+ if (!global.simply) {
+ global.simply = {};
}
- return el;
+ global.simply.keyboard = keyboard;
}
-
- document.addEventListener('change', function(evt) {
- var root = null;
- var name = '';
- if (evt.target.dataset.simplyElement) {
- root = findCollection(evt.target);
- if (root && root.dataset) {
- name = root.dataset.simplyCollection;
+})(this);
+(function(global) {
+ 'use strict';
+
+ var defaultActions = {
+ 'simply-hide': function(el) {
+ el.classList.remove('simply-visible');
+ return Promise.resolve();
+ },
+ 'simply-show': function(el) {
+ el.classList.add('simply-visible');
+ return Promise.resolve();
+ },
+ 'simply-select': function(el,group,target,targetGroup) {
+ if (group) {
+ this.call('simply-deselect', this.app.container.querySelectorAll('[data-simply-group='+group+']'));
}
- }
- if (name && knownCollections[name]) {
- var inputs = root.querySelectorAll('[data-simply-element]');
- var elements = [].reduce.call(inputs, function(elements, input) {
- elements[input.dataset.simplyElement] = input;
- return elements;
- }, {});
- for (var i=knownCollections[name].length-1; i>=0; i--) {
- var result = knownCollections[name][i].call(evt.target.form, elements);
- if (result === false) {
+ el.classList.add('simply-selected');
+ if (target) {
+ this.call('simply-select',target,targetGroup);
+ }
+ return Promise.resolve();
+ },
+ 'simply-toggle-select': function(el,group,target,targetGroup) {
+ if (!el.classList.contains('simply-selected')) {
+ this.call('simply-select',el,group,target,targetGroup);
+ } else {
+ this.call('simply-deselect',el,target);
+ }
+ return Promise.resolve();
+ },
+ 'simply-toggle-class': function(el,className,target) {
+ if (!target) {
+ target = el;
+ }
+ return Promise.resolve(target.classList.toggle(className));
+ },
+ 'simply-deselect': function(el,target) {
+ if ( typeof el.length=='number' && typeof el.item=='function') {
+ el = Array.prototype.slice.call(el);
+ }
+ if ( Array.isArray(el) ) {
+ for (var i=0,l=el.length; i1 && curr) {
- var key = parts.shift();
- if (typeof curr[key] == 'undefined' || curr[key]==null) {
- curr[key] = {};
- }
- curr = curr[key];
+})(this);
+(function(global) {
+ 'use strict';
+
+ var resize = function(app, config) {
+ if (!config) {
+ config = {};
+ }
+ if (!config.sizes) {
+ config.sizes = {
+ 'simply-tiny' : 0,
+ 'simply-xsmall' : 480,
+ 'simply-small' : 768,
+ 'simply-medium' : 992,
+ 'simply-large' : 1200
+ };
}
- curr[parts.shift()] = value;
- }
- function setValue(el, value, binding) {
- if (el!=focusedElement) {
- var fieldType = getFieldType(binding.fieldTypes, el);
- if (fieldType) {
- fieldType.set.call(el, (typeof value != 'undefined' ? value : ''), binding);
- el.dispatchEvent(new Event('simply.bind.resolved', {
- bubbles: true,
- cancelable: false
- }));
+ var lastSize = 0;
+ function resizeSniffer() {
+ var size = app.container.getBoundingClientRect().width;
+ if ( lastSize==size ) {
+ return;
+ }
+ lastSize = size;
+ var sizes = Object.keys(config.sizes);
+ var match = sizes.pop();
+ while (match) {
+ if ( size=0;i--) {
- if (el.matches(setters[i])) {
- return binding.fieldTypes[setters[i]].get.call(el);
- }
+ if ( global.attachEvent ) {
+ app.container.attachEvent('onresize', resizeSniffer);
+ } else {
+ global.setInterval(resizeSniffer, 200);
}
- }
- function getFieldType(fieldTypes, el) {
- var setters = Object.keys(fieldTypes);
- for(var i=setters.length-1;i>=0;i--) {
- if (el.matches(setters[i])) {
- return fieldTypes[setters[i]];
- }
+ if ( simply.toolbar ) {
+ var toolbars = app.container.querySelectorAll('.simply-toolbar');
+ [].forEach.call(toolbars, function(toolbar) {
+ simply.toolbar.init(toolbar);
+ if (simply.toolbar.scroll) {
+ simply.toolbar.scroll(toolbar);
+ }
+ });
}
- return null;
- }
- function getPath(el, attribute) {
- var attributes = attribute.split(',');
- for (var attr of attributes) {
- if (el.hasAttribute(attr)) {
- return el.getAttribute(attr);
- }
+ return resizeSniffer;
+ };
+
+ if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
+ module.exports = resize;
+ } else {
+ if (!global.simply) {
+ global.simply = {};
}
- return null;
+ global.simply.resize = resize;
}
+})(this);(function (global) {
+ 'use strict';
- function throttle( callbackFunction, intervalTime ) {
+ var throttle = function( callbackFunction, intervalTime ) {
var eventId = 0;
return function() {
var myArguments = arguments;
@@ -1236,7 +1373,7 @@ this.simply = (function(simply, global) {
}, intervalTime );
}
};
- }
+ };
var runWhenIdle = (function() {
if (global.requestIdleCallback) {
@@ -1247,165 +1384,649 @@ this.simply = (function(simply, global) {
return global.requestAnimationFrame;
})();
- function Binding(config, force) {
- this.config = config;
- if (!this.config) {
- this.config = {};
+ var rebaseHref = function(relative, base) {
+ let url = new URL(relative, base)
+ if (include.cacheBuster) {
+ url.searchParams.set('cb',include.cacheBuster)
+ }
+ return url.href
+ };
+
+ var observer, loaded = {};
+ var head = global.document.querySelector('head');
+ var currentScript = global.document.currentScript;
+ if (!currentScript) {
+ var getScriptURL = (function() {
+ var scripts = document.getElementsByTagName('script');
+ var index = scripts.length - 1;
+ var myScript = scripts[index];
+ return function() { return myScript.src; };
+ })();
+ var currentScriptURL = getScriptURL();
+ } else {
+ var currentScriptURL = currentScript.src;
+ }
+
+ var waitForPreviousScripts = function() {
+ // because of the async=false attribute, this script will run after
+ // the previous scripts have been loaded and run
+ // simply.include.next.js only fires the simply-next-script event
+ // that triggers the Promise.resolve method
+ return new Promise(function(resolve) {
+ var next = global.document.createElement('script');
+ next.src = rebaseHref('simply.include.next.js', currentScriptURL);
+ next.async = false;
+ global.document.addEventListener('simply-include-next', function() {
+ head.removeChild(next);
+ resolve();
+ }, { once: true, passive: true});
+ head.appendChild(next);
+ });
+ };
+
+ var scriptLocations = [];
+
+ var include = {
+ cacheBuster: null,
+ scripts: function(scripts, base) {
+ var arr = [];
+ for(var i = scripts.length; i--; arr.unshift(scripts[i]));
+ var importScript = function() {
+ var script = arr.shift();
+ if (!script) {
+ return;
+ }
+ var attrs = [].map.call(script.attributes, function(attr) {
+ return attr.name;
+ });
+ var clone = global.document.createElement('script');
+ attrs.forEach(function(attr) {
+ clone.setAttribute(attr, script.getAttribute(attr));
+ });
+ clone.removeAttribute('data-simply-location');
+ if (!clone.src) {
+ // this is an inline script, so copy the content and wait for previous scripts to run
+ clone.innerHTML = script.innerHTML;
+ waitForPreviousScripts()
+ .then(function() {
+ var node = scriptLocations[script.dataset.simplyLocation];
+ node.parentNode.insertBefore(clone, node);
+ node.parentNode.removeChild(node);
+ importScript();
+ });
+ } else {
+ clone.src = rebaseHref(clone.src, base);
+ if (!clone.hasAttribute('async') && !clone.hasAttribute('defer')) {
+ clone.async = false; //important! do not use clone.setAttribute('async', false) - it has no effect
+ }
+ var node = scriptLocations[script.dataset.simplyLocation];
+ node.parentNode.insertBefore(clone, node);
+ node.parentNode.removeChild(node);
+ loaded[clone.src]=true;
+ importScript();
+ }
+ };
+ if (arr.length) {
+ importScript();
+ }
+ },
+ html: function(html, link) {
+ var fragment = global.document.createRange().createContextualFragment(html);
+ var stylesheets = fragment.querySelectorAll('link[rel="stylesheet"],style');
+ // add all stylesheets to head
+ [].forEach.call(stylesheets, function(stylesheet) {
+ if (stylesheet.href) {
+ stylesheet.href = rebaseHref(stylesheet.href, link.href);
+ }
+ head.appendChild(stylesheet);
+ });
+ // remove the scripts from the fragment, as they will not run in the
+ // order in which they are defined
+ var scriptsFragment = global.document.createDocumentFragment();
+ // FIXME: this loses the original position of the script
+ // should add a placeholder so we can reinsert the clone
+ var scripts = fragment.querySelectorAll('script');
+ [].forEach.call(scripts, function(script) {
+ var placeholder = global.document.createComment(script.src || 'inline script');
+ script.parentNode.insertBefore(placeholder, script);
+ script.dataset.simplyLocation = scriptLocations.length;
+ scriptLocations.push(placeholder);
+ scriptsFragment.appendChild(script);
+ });
+ // add the remainder before the include link
+ link.parentNode.insertBefore(fragment, link ? link : null);
+ global.setTimeout(function() {
+ if (global.editor && global.editor.data && fragment.querySelector('[data-simply-field],[data-simply-list]')) {
+ //TODO: remove this dependency and let simply.bind listen for dom node insertions (and simply-edit.js use simply.bind)
+ global.editor.data.apply(global.editor.currentData, global.document);
+ }
+ simply.include.scripts(scriptsFragment.childNodes, link ? link.href : global.location.href );
+ }, 10);
}
- if (!this.config.model) {
- this.config.model = {};
+ };
+
+ var included = {};
+ var includeLinks = function(links) {
+ // mark them as in progress, so handleChanges doesn't find them again
+ var remainingLinks = [].reduce.call(links, function(remainder, link) {
+ if (link.rel=='simply-include-once' && included[link.href]) {
+ link.parentNode.removeChild(link);
+ } else {
+ included[link.href]=true;
+ link.rel = 'simply-include-loading';
+ remainder.push(link);
+ }
+ return remainder;
+ }, []);
+ [].forEach.call(remainingLinks, function(link) {
+ if (!link.href) {
+ return;
+ }
+ // fetch the html
+ fetch(link.href)
+ .then(function(response) {
+ if (response.ok) {
+ console.log('simply-include: loaded '+link.href);
+ return response.text();
+ } else {
+ console.log('simply-include: failed to load '+link.href);
+ }
+ })
+ .then(function(html) {
+ // if succesfull import the html
+ simply.include.html(html, link);
+ // remove the include link
+ link.parentNode.removeChild(link);
+ });
+ });
+ };
+
+ var handleChanges = throttle(function() {
+ runWhenIdle(function() {
+ var links = global.document.querySelectorAll('link[rel="simply-include"],link[rel="simply-include-once"]');
+ if (links.length) {
+ includeLinks(links);
+ }
+ });
+ });
+
+ var observe = function() {
+ observer = new MutationObserver(handleChanges);
+ observer.observe(global.document, {
+ subtree: true,
+ childList: true,
+ });
+ };
+
+ observe();
+ handleChanges(); // check if there are include links in the dom already
+
+ if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
+ module.exports = include;
+ } else {
+ if (!global.simply) {
+ global.simply = {};
}
- if (!this.config.attribute) {
- this.config.attribute = 'data-simply-bind';
+ global.simply.include = include;
+ }
+
+
+})(this);
+(function(global) {
+ 'use strict';
+ var view = function(app, view) {
+
+ app.view = view || {};
+
+ var load = function() {
+ var data = app.view;
+ var path = global.editor.data.getDataPath(app.container);
+ app.view = global.editor.currentData[path];
+ Object.keys(data).forEach(function(key) {
+ app.view[key] = data[key];
+ });
+ };
+
+ if (global.editor && global.editor.currentData) {
+ load();
+ } else {
+ global.document.addEventListener('simply-content-loaded', function() {
+ load();
+ });
}
- if (!this.config.selector) {
- this.config.selector = '[data-simply-bind]';
+
+ return app.view;
+ };
+
+ if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
+ module.exports = view;
+ } else {
+ if (!global.simply) {
+ global.simply = {};
}
- if (!this.config.container) {
- this.config.container = document;
+ global.simply.view = view;
+ }
+})(this);
+(function(global) {
+ 'use strict';
+
+ function etag() {
+ let d = '';
+ while (d.length < 32) d += Math.random().toString(16).substr(2);
+ const vr = ((parseInt(d.substr(16, 1), 16) & 0x3) | 0x8).toString(16);
+ return `${d.substr(0, 8)}-${d.substr(8, 4)}-4${d.substr(13, 3)}-${vr}${d.substr(17, 3)}-${d.substr(20, 12)}`;
+ }
+
+ function ViewModel(name, data, options) {
+ this.name = name;
+ this.data = data || [];
+ this.data.etag = etag();
+ this.view = {
+ options: {},
+ data: [] //Array.from(this.data).slice()
+ };
+ this.options = options || {};
+ this.plugins = {
+ start: [],
+ select: [],
+ order: [],
+ render: [],
+ finish: []
+ };
+ }
+
+ ViewModel.prototype.update = function(params) {
+ if (!params) {
+ params = {};
}
- if (typeof this.config.twoway == 'undefined') {
- this.config.twoway = true;
+ if (params.data) {
+ // this.data is a reference to the data passed, so that any changes in it will get applied
+ // to the original
+ this.data = params.data;
+ this.data.etag = etag()
}
- this.fieldTypes = {
- '*': {
- set: function(value) {
- this.innerHTML = value;
- },
- get: function() {
- return this.innerHTML;
+ // the view is a shallow copy of the array, so that changes in sort order and filtering
+ // won't get applied to the original, but databindings on its children will still work
+ this.view.data = Array.from(this.data).slice();
+ this.view.data.etag = this.data.etag;
+ let data = this.view.data;
+ let plugins = this.plugins.start.concat(this.plugins.select, this.plugins.order, this.plugins.render, this.plugins.finish);
+ plugins.forEach(plugin => {
+ data = plugin.call(this, params, data);
+ if (!data) {
+ data = this.view.data;
+ }
+ this.view.data = data
+ });
+
+ if (global.editor) {
+ global.editor.addDataSource(this.name,{
+ load: function(el, callback) {
+ callback(self.view.data);
}
+ });
+ updateDataSource(this.name);
+ }
+ };
+
+ ViewModel.prototype.addPlugin = function(pipe, plugin) {
+ if (typeof this.plugins[pipe] == 'undefined') {
+ throw new Error('Unknown pipeline '+pipe);
+ }
+ this.plugins[pipe].push(plugin);
+ };
+
+ ViewModel.prototype.removePlugin = function(pipe, plugin) {
+ if (typeof this.plugins[pipe] == 'undefined') {
+ throw new Error('Unknown pipeline '+pipe);
+ }
+ this.plugins[pipe] = this.plugins[pipe].filter(function(p) {
+ return p != plugin;
+ });
+ };
+
+ var updateDataSource = function(name) {
+ global.document.querySelectorAll('[data-simply-data="'+name+'"]').forEach(function(list) {
+ global.editor.list.applyDataSource(list, name);
+ });
+ };
+
+ var createSort = function(options) {
+ var defaultOptions = {
+ name: 'sort',
+ getSort: function(params) {
+ return Array.prototype.sort;
}
};
- if (this.config.fieldTypes) {
- Object.assign(this.fieldTypes, this.config.fieldTypes);
- }
- this.attach(this.config.container.querySelectorAll(this.config.selector), this.config.model, force);
- if (this.config.twoway) {
- var self = this;
- var observer = new MutationObserver(
- throttle(function() {
- runWhenIdle(function() {
- self.attach(self.config.container.querySelectorAll(self.config.selector), self.config.model);
- });
- })
- );
- observer.observe(this.config.container, {
- subtree: true,
- childList: true
- });
+ options = Object.assign(defaultOptions, options || {});
+
+ return function(params) {
+ this.options[options.name] = options;
+ if (params[options.name]) {
+ options = Object.assign(options, params[options.name]);
+ }
+ this.view.data.sort(options.getSort.call(this, options));
+ };
+ };
+
+ var createPaging = function(options) {
+ var defaultOptions = {
+ name: 'paging',
+ page: 1,
+ pageSize: 100,
+ max: 1,
+ prev: 0,
+ next: 0
+ };
+ options = Object.assign(defaultOptions, options || {});
+
+ return function(params) {
+ this.options[options.name] = options;
+ if (this.view.data) {
+ options.max = Math.max(1, Math.ceil(Array.from(this.view.data).length / options.pageSize));
+ } else {
+ options.max = 1;
+ }
+ if (this.view.changed) {
+ options.page = 1; // reset to page 1 when something in the view data has changed
+ }
+ if (params[options.name]) {
+ options = Object.assign(options, params[options.name]);
+ }
+ options.page = Math.max(1, Math.min(options.max, options.page)); // clamp page nr
+ options.prev = options.page - 1; // calculate previous page, 0 is allowed
+ if (options.page {};
+ cache.$options = Object.assign({}, options);
+ return new Proxy( cache, getApiHandler(cache.$options) );
+ },
- var attachElement = function(jsonPath) {
- el.dataset.simplyBound = true;
- initElement(el);
- setValue(el, getByPath(model, jsonPath), self);
- simply.observe(model, jsonPath, function(value) {
- if (el != focusedElement) {
- setValue(el, value, self);
+ /**
+ * Fetches the options.baseURL using the fetch api and returns a promise
+ * Extra options in addition to those of global.fetch():
+ * - user (and password): if set, a basic authentication header will be added
+ * - paramsFormat: either 'formData', 'json' or 'search'
+ * By default params, if set, will be added to the baseURL as searchParams
+ * @param method one of the http verbs, e.g. get, post, etc.
+ * @param options the options for fetch(), with some additions
+ * @param params the parameters to send with the request, as javascript/json data
+ * @return Promise
+ */
+ fetch: function(method, params, options) {
+ if (!options.url) {
+ if (!options.baseURL) {
+ throw new Error('No url or baseURL in options object');
}
- });
- };
-
- var addMutationObserver = function(jsonPath) {
- if (el.dataset.simplyList) {
- return;
- }
- var update = throttle(function() {
- runWhenIdle(function() {
- var v = getValue(el, self);
- var s = getByPath(model, jsonPath);
- if (v != s) {
- focusedElement = el;
- setByPath(model, jsonPath, v);
- focusedElement = null;
- }
- });
- }, 250);
- var observer = new MutationObserver(function() {
- if (observersPaused) {
- return;
+ while (options.baseURL[options.baseURL.length-1]=='/') {
+ options.baseURL = options.baseURL.substr(0, options.baseURL.length-1);
}
- update();
- });
- observer.observe(el, {
- characterData: true,
- subtree: true,
- childList: true,
- attributes: true
- });
- if (!observers.has(el)) {
- observers.set(el, []);
+ var url = new URL(options.baseURL+options.path);
+ } else {
+ var url = options.url;
}
- observers.get(el).push(observer);
- return observer;
- };
-
- /**
- * Runs the init() method of the fieldType, if it is defined.
- **/
- var initElement = function(el) {
- if (initialized.has(el)) {
- return;
+ var fetchOptions = Object.assign({}, options);
+ if (!fetchOptions.headers) {
+ fetchOptions.headers = {};
}
- initialized.set(el, true);
- var selectors = Object.keys(self.fieldTypes);
- for (var i=selectors.length-1; i>=0; i--) {
- if (self.fieldTypes[selectors[i]].init && el.matches(selectors[i])) {
- self.fieldTypes[selectors[i]].init.call(el, self);
- return;
+ if (params) {
+ if (method=='GET') {
+ var paramsFormat = 'search';
+ } else {
+ var paramsFormat = options.paramsFormat;
+ }
+ switch(paramsFormat) {
+ case 'formData':
+ var formData = new FormData();
+ for (const name in params) {
+ formData.append(name, params[name]);
+ }
+ if (!fetchOptions.headers['Content-Type']) {
+ fetchOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded';
+ }
+ break;
+ case 'json':
+ var formData = JSON.stringify(params);
+ if (!fetchOptions.headers['Content-Type']) {
+ fetchOptions.headers['Content-Type'] = 'application/json';
+ }
+ break;
+ case 'search':
+ var searchParams = url.searchParams; //new URLSearchParams(url.search.slice(1));
+ for (const name in params) {
+ searchParams.set(name, params[name]);
+ }
+ url.search = searchParams.toString();
+ break;
+ default:
+ throw Error('Unknown options.paramsFormat '+options.paramsFormat+'. Select one of formData, json or search.');
+ break;
}
}
- };
-
- var self = this;
- if (el instanceof HTMLElement) {
- if (!force && el.dataset.simplyBound) {
- return;
+ if (formData) {
+ fetchOptions.body = formData
}
- var jsonPath = getPath(el, this.config.attribute);
- if (illegalNesting(el)) {
- el.dataset.simplyBound = 'Error: nested binding';
- console.error('Error: found nested data-binding element:',el);
- return;
+ if (options.user) {
+ fetchOptions.headers['Authorization'] = 'Basic '+btoa(options.user+':'+options.password);
}
- attachElement(jsonPath);
- if (this.config.twoway) {
- addMutationObserver(jsonPath);
+ fetchOptions.method = method.toUpperCase();
+ var fetchURL = url.toString()
+ return fetch(fetchURL, fetchOptions);
+ },
+ /**
+ * Creates a function to call one or more graphql queries
+ */
+ graphqlQuery: function(url, query, options) {
+ options = Object.assign({ paramsFormat: 'json', url: url, responseFormat: 'json' }, options);
+ return function(params, operationName) {
+ let postParams = {
+ query: query
+ };
+ if (operationName) {
+ postParams.operationName = operationName;
+ }
+ postParams.variables = params || {};
+ return simply.api.fetch('POST', postParams, options )
+ .then(function(response) {
+ return simply.api.getResult(response, options);
+ });
+ }
+ },
+ /**
+ * Handles the response and returns a Promise with the response data as specified
+ * @param response Response
+ * @param options
+ * - responseFormat: one of 'text', 'formData', 'blob', 'arrayBuffer', 'unbuffered' or 'json'.
+ * The default is json.
+ */
+ getResult: function(response, options) {
+ if (response.ok) {
+ switch(options.responseFormat) {
+ case 'text':
+ return response.text();
+ break;
+ case 'formData':
+ return response.formData();
+ break;
+ case 'blob':
+ return response.blob();
+ break;
+ case 'arrayBuffer':
+ return response.arrayBuffer();
+ break;
+ case 'unbuffered':
+ return response.body;
+ break;
+ case 'json':
+ default:
+ return response.json();
+ break;
+ }
+ } else {
+ throw {
+ status: response.status,
+ message: response.statusText,
+ response: response
+ }
}
- } else {
- [].forEach.call(el, function(element) {
- self.attach(element, model, force);
- });
+ },
+ logError: function(error, options) {
+ console.error(error.status, error.message);
}
- };
+ }
- Binding.prototype.pauseObservers = function() {
- observersPaused++;
+ var defaultOptions = {
+ path: '',
+ responseFormat: 'json',
+ paramsFormat: 'search',
+ verbs: ['get','post'],
+ handlers: {
+ fetch: api.fetch,
+ result: api.getResult,
+ error: api.logError
+ }
};
- Binding.prototype.resumeObservers = function() {
- observersPaused--;
- };
+ function cd(path, name) {
+ name = name.replace(/\//g,'');
+ if (!path.length || path[path.length-1]!=='/') {
+ path+='/';
+ }
+ return path+encodeURIComponent(name);
+ }
- simply.bind = function(config, force) {
- return new Binding(config, force);
- };
+ function fetchChain(prop, params) {
+ var options = this;
+ return this.handlers.fetch
+ .call(this, prop, params, options)
+ .then(function(res) {
+ return options.handlers.result.call(options, res, options);
+ })
+ .catch(function(error) {
+ return options.handlers.error.call(options, error, options);
+ });
+ }
+
+ function getApiHandler(options) {
+ options.handlers = Object.assign({}, defaultOptions.handlers, options.handlers);
+ options = Object.assign({}, defaultOptions, options);
+
+ return {
+ get: function(cache, prop) {
+ if (!cache[prop]) {
+ if (options.verbs.indexOf(prop)!=-1) {
+ cache[prop] = function(params) {
+ return fetchChain.call(options, prop, params);
+ }
+ } else {
+ cache[prop] = api.proxy(Object.assign({}, options, {
+ path: cd(options.path, prop)
+ }));
+ }
+ }
+ return cache[prop];
+ },
+ apply: function(cache, thisArg, params) {
+ return fetchChain.call(options, 'get', params[0] ? params[0] : null)
+ }
+ }
+ }
+
+
+ if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
+ module.exports = api;
+ } else {
+ if (!global.simply) {
+ global.simply = {};
+ }
+ global.simply.api = api;
+ }
+
+})(this);
+(function(global) {
+ 'use strict';
- return simply;
-})(this.simply || {}, this);this.simply = (function(simply, global) {
- simply.app = function(options) {
+ var app = function(options) {
if (!options) {
options = {};
}
@@ -1419,12 +2040,22 @@ this.simply = (function(simply, global) {
}
if ( options.routes ) {
simply.route.load(options.routes);
+ if (options.routeEvents) {
+ Object.keys(options.routeEvents).forEach(function(action) {
+ Object.keys(options.routeEvents[action]).forEach(function(route) {
+ options.routeEvents[action][route].forEach(function(callback) {
+ simply.route.addListener(action, route, callback);
+ });
+ });
+ });
+ }
simply.route.handleEvents();
global.setTimeout(function() {
simply.route.match(global.location.pathname+global.location.hash);
- },1);
+ });
}
- this.container = options.container || document.body;
+ this.container = options.container || document.body;
+ this.keyboard = simply.keyboard ? simply.keyboard(this, options.keyboard || {}) : false;
this.actions = simply.action ? simply.action(this, options.actions) : false;
this.commands = simply.command ? simply.command(this, options.commands) : false;
this.resize = simply.resize ? simply.resize(this, options.resize) : false;
@@ -1442,187 +2073,16 @@ this.simply = (function(simply, global) {
return this.container.querySelector('[data-simply-id='+id+']') || document.getElementById(id);
};
- var app = new simplyApp(options);
-
- return app;
- };
-
- return simply;
-})(this.simply || {}, this);
-this.simply = (function(simply, global) {
-
- var listeners = {};
-
- simply.activate = {
- addListener: function(name, callback) {
- if (!listeners[name]) {
- listeners[name] = [];
- }
- listeners[name].push(callback);
- initialCall(name);
- },
- removeListener: function(name, callback) {
- if (!listeners[name]) {
- return false;
- }
- listeners[name] = listeners[name].filter(function(listener) {
- return listener!=callback;
- });
- }
- };
-
- var initialCall = function(name) {
- var nodes = document.querySelectorAll('[data-simply-activate="'+name+'"]');
- if (nodes) {
- [].forEach.call(nodes, function(node) {
- callListeners(node);
- });
- }
- };
-
- var callListeners = function(node) {
- if (node && node.dataset.simplyActivate
- && listeners[node.dataset.simplyActivate]
- ) {
- listeners[node.dataset.simplyActivate].forEach(function(callback) {
- callback.call(node);
- });
- }
- };
-
- var handleChanges = function(changes) {
- var activateNodes = [];
- for (var change of changes) {
- if (change.type=='childList') {
- [].forEach.call(change.addedNodes, function(node) {
- if (node.querySelectorAll) {
- var toActivate = [].slice.call(node.querySelectorAll('[data-simply-activate]'));
- if (node.matches('[data-simply-activate]')) {
- toActivate.push(node);
- }
- activateNodes = activateNodes.concat(toActivate);
- }
- });
- }
- }
- if (activateNodes.length) {
- activateNodes.forEach(function(node) {
- callListeners(node);
- });
- }
- };
-
- var observer = new MutationObserver(handleChanges);
- observer.observe(document, {
- subtree: true,
- childList: true
- });
-
- return simply;
-})(this.simply || {}, this);
-this.simply = (function(simply, global) {
- var defaultActions = {
- 'simply-hide': function(el) {
- el.classList.remove('simply-visible');
- return Promise.resolve();
- },
- 'simply-show': function(el) {
- el.classList.add('simply-visible');
- return Promise.resolve();
- },
- 'simply-select': function(el,group,target,targetGroup) {
- if (group) {
- this.call('simply-deselect', this.app.container.querySelectorAll('[data-simply-group='+group+']'));
- }
- el.classList.add('simply-selected');
- if (target) {
- this.call('simply-select',target,targetGroup);
- }
- return Promise.resolve();
- },
- 'simply-toggle-select': function(el,group,target,targetGroup) {
- if (!el.classList.contains('simply-selected')) {
- this.call('simply-select',el,group,target,targetGroup);
- } else {
- this.call('simply-deselect',el,target);
- }
- return Promise.resolve();
- },
- 'simply-toggle-class': function(el,className,target) {
- if (!target) {
- target = el;
- }
- return Promise.resolve(target.classList.toggle(className));
- },
- 'simply-deselect': function(el,target) {
- if ( typeof el.length=='number' && typeof el.item=='function') {
- el = Array.prototype.slice.call(el);
- }
- if ( Array.isArray(el) ) {
- for (var i=0,l=el.length; iget(AppConfig::class)->getValueBool(self::APP_ID, 'userSubDomainsEnabled');
}
}
diff --git a/solid/lib/BaseServerConfig.php b/solid/lib/BaseServerConfig.php
index 2b9de07c..7c391893 100644
--- a/solid/lib/BaseServerConfig.php
+++ b/solid/lib/BaseServerConfig.php
@@ -1,14 +1,18 @@
config = $config;
}
@@ -91,7 +95,7 @@ public function getClients() {
$clients[] = [
"clientId" => $matches[1],
"clientName" => $clientRegistration['client_name'],
- "clientBlocked" => $clientRegistration['blocked']
+ "clientBlocked" => $clientRegistration['blocked'] ?? false,
];
}
}
@@ -152,6 +156,7 @@ public function removeClientConfig($clientId) {
unset($scopes[$clientId]);
$this->config->setAppValue('solid', 'clientScopes', $scopes);
}
+
public function saveClientRegistration($origin, $clientData) {
$originHash = md5($origin);
$existingRegistration = $this->getClientRegistration($originHash);
@@ -182,4 +187,57 @@ public function getClientRegistration($clientId) {
$data = $this->config->getAppValue('solid', "client-" . $clientId, "{}");
return json_decode($data, true);
}
+
+ public function getUserSubDomainsEnabled() {
+ $value = $this->config->getAppValue('solid', 'userSubDomainsEnabled', false);
+
+ return $this->castToBool($value);
+ }
+
+ public function setUserSubDomainsEnabled($enabled) {
+ $value = $this->castToBool($enabled);
+
+ $this->config->setAppValue('solid', 'userSubDomainsEnabled', $value);
+ }
+
+ ////////////////////////////// UTILITY METHODS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+
+ private function castToBool(string $mixedValue): bool
+ {
+ $type = gettype($mixedValue);
+
+ if ($type === 'boolean' || $type === 'NULL' || $type === 'integer') {
+ $value = (bool) $mixedValue;
+ } else {
+ if ($type === 'string') {
+ $mixedValue = strtolower($mixedValue);
+ if ($mixedValue === 'true' || $mixedValue === '1') {
+ $value = true;
+ } elseif ($mixedValue === 'false' || $mixedValue === '0' || $mixedValue === '') {
+ $value = false;
+ } else {
+ $error = [
+ 'invalid' => 'value',
+ 'for' => 'userSubDomainsEnabled',
+ 'received' => $mixedValue,
+ 'expected' => implode(',', ['true', 'false', '1', '0'])
+ ];
+ }
+ } else {
+ $error = [
+ 'invalid' => 'type',
+ 'for' => 'userSubDomainsEnabled',
+ 'received' => $type,
+ 'expected' => implode(',', ['boolean', 'NULL', 'integer', 'string'])
+ ];
+ }
+ }
+
+ if (isset($error)) {
+ $errorMessage = vsprintf(self::ERROR_INVALID_ARGUMENT, $error);
+ throw new InvalidArgumentException($errorMessage);
+ }
+
+ return $value;
+ }
}
diff --git a/solid/lib/Controller/AppController.php b/solid/lib/Controller/AppController.php
index 94addc28..db34f5bd 100644
--- a/solid/lib/Controller/AppController.php
+++ b/solid/lib/Controller/AppController.php
@@ -2,35 +2,38 @@
namespace OCA\Solid\Controller;
use OCA\Solid\ServerConfig;
-use OCP\IRequest;
-use OCP\IUserManager;
-use OCP\Contacts\IManager;
-use OCP\IURLGenerator;
-use OCP\IConfig;
-use OCP\AppFramework\Http;
-use OCP\AppFramework\Http\TemplateResponse;
-use OCP\AppFramework\Http\DataResponse;
+
use OCP\AppFramework\Controller;
-use OCP\AppFramework\Http\JSONResponse;
+use OCP\AppFramework\Http;
use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\Contacts\IManager;
+use OCP\IConfig;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+use OCP\IUserManager;
class AppController extends Controller {
+ use GetStorageUrlTrait;
+
+ protected ServerConfig $config;
+ protected IURLGenerator $urlGenerator;
+
private $userId;
private $userManager;
- private $urlGenerator;
- private $config;
- public function __construct($AppName, IRequest $request, IConfig $config, IUserManager $userManager, IManager $contactsManager, IURLGenerator $urlGenerator, $userId){
+ public function __construct($AppName, IRequest $request, IConfig $config, IUserManager $userManager, IManager $contactsManager, IURLGenerator $urlGenerator, $userId) {
parent::__construct($AppName, $request);
$this->userId = $userId;
$this->userManager = $userManager;
$this->contactsManager = $contactsManager;
$this->request = $request;
$this->urlGenerator = $urlGenerator;
- $this->config = new \OCA\Solid\ServerConfig($config, $urlGenerator, $userManager);
+ $this->config = new ServerConfig($config, $urlGenerator, $userManager);
}
- private function getUserApps($userId) {
+ private function getUserApps($userId) {
$userApps = [];
if ($this->userManager->userExists($userId)) {
$allowedClients = $this->config->getAllowedClients($userId);
@@ -46,7 +49,7 @@ private function getAppsList() {
$path = __DIR__ . "/../solid-app-list.json";
$appsListJson = file_get_contents($path);
$appsList = json_decode($appsListJson, true);
-
+
$userApps = $this->getUserApps($this->userId);
foreach ($appsList as $key => $app) {
@@ -64,11 +67,7 @@ private function getAppsList() {
private function getProfilePage() {
return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("solid.profile.handleGet", array("userId" => $this->userId, "path" => "/card"))) . "#me";
}
- private function getStorageUrl($userId) {
- $storageUrl = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("solid.storage.handleHead", array("userId" => $userId, "path" => "foo")));
- $storageUrl = preg_replace('/foo$/', '', $storageUrl);
- return $storageUrl;
- }
+
/**
* @NoAdminRequired
* @NoCSRFRequired
diff --git a/solid/lib/Controller/CalendarController.php b/solid/lib/Controller/CalendarController.php
index a9e773bb..046cef08 100644
--- a/solid/lib/Controller/CalendarController.php
+++ b/solid/lib/Controller/CalendarController.php
@@ -185,20 +185,31 @@ public function handlePost($userId, $path) {
* @NoAdminRequired
* @NoCSRFRequired
*/
- public function handlePut() { // $userId, $path) {
- // FIXME: Adding the correct variables in the function name will make nextcloud
- // throw an error about accessing put twice, so we will find out the userId and path from $_SERVER instead;
-
- // because we got here, the request uri should look like:
- // /index.php/apps/solid/@{userId}/storage{path}
- $pathInfo = explode("@", $_SERVER['REQUEST_URI']);
- $pathInfo = explode("/", $pathInfo[1], 2);
- $userId = $pathInfo[0];
- $path = $pathInfo[1];
- $path = preg_replace("/^calendar/", "", $path);
-
- return $this->handleRequest($userId, $path);
- }
+ public function handlePut() { // $userId, $path) {
+ // FIXME: Adding the correct variables in the function name will make nextcloud
+ // throw an error about accessing put twice, so we will find out the userId and path from $_SERVER instead;
+
+ // because we got here, the request uri should look like:
+ // - if we have user subdomains enabled:
+ // /index.php/apps/solid/calendar{path}
+ // and otherwise:
+ // index.php/apps/solid/~{userId}/calendar{path}
+
+ // In the first case, we'll get the username from the SERVER_NAME. In the latter, it will come from the URL;
+ if ($this->config->getUserSubDomainsEnabled()) {
+ $pathInfo = explode("calendar/", $_SERVER['REQUEST_URI']);
+ $path = $pathInfo[1];
+ $userId = explode(".", $_SERVER['SERVER_NAME'])[0];
+ } else {
+ $pathInfo = explode("~", $_SERVER['REQUEST_URI']);
+ $pathInfo = explode("/", $pathInfo[1], 2);
+ $userId = $pathInfo[0];
+ $path = $pathInfo[1];
+ $path = preg_replace("/^calendar/", "", $path);
+ }
+
+ return $this->handleRequest($userId, $path);
+ }
/**
* @PublicPage
* @NoAdminRequired
diff --git a/solid/lib/Controller/ContactsController.php b/solid/lib/Controller/ContactsController.php
index 1fc3fcec..3d2f52bb 100644
--- a/solid/lib/Controller/ContactsController.php
+++ b/solid/lib/Controller/ContactsController.php
@@ -186,20 +186,31 @@ public function handlePost($userId, $path) {
* @NoAdminRequired
* @NoCSRFRequired
*/
- public function handlePut() { // $userId, $path) {
- // FIXME: Adding the correct variables in the function name will make nextcloud
- // throw an error about accessing put twice, so we will find out the userId and path from $_SERVER instead;
-
- // because we got here, the request uri should look like:
- // /index.php/apps/solid/@{userId}/storage{path}
- $pathInfo = explode("@", $_SERVER['REQUEST_URI']);
- $pathInfo = explode("/", $pathInfo[1], 2);
- $userId = $pathInfo[0];
- $path = $pathInfo[1];
- $path = preg_replace("/^contacts/", "", $path);
-
- return $this->handleRequest($userId, $path);
- }
+ public function handlePut() { // $userId, $path) {
+ // FIXME: Adding the correct variables in the function name will make nextcloud
+ // throw an error about accessing put twice, so we will find out the userId and path from $_SERVER instead;
+
+ // because we got here, the request uri should look like:
+ // - if we have user subdomains enabled:
+ // /index.php/apps/solid/contacts{path}
+ // and otherwise:
+ // index.php/apps/solid/~{userId}/contacts{path}
+
+ // In the first case, we'll get the username from the SERVER_NAME. In the latter, it will come from the URL;
+ if ($this->config->getUserSubDomainsEnabled()) {
+ $pathInfo = explode("contacts/", $_SERVER['REQUEST_URI']);
+ $path = $pathInfo[1];
+ $userId = explode(".", $_SERVER['SERVER_NAME'])[0];
+ } else {
+ $pathInfo = explode("~", $_SERVER['REQUEST_URI']);
+ $pathInfo = explode("/", $pathInfo[1], 2);
+ $userId = $pathInfo[0];
+ $path = $pathInfo[1];
+ $path = preg_replace("/^contacts/", "", $path);
+ }
+
+ return $this->handleRequest($userId, $path);
+ }
/**
* @PublicPage
* @NoAdminRequired
diff --git a/solid/lib/Controller/GetStorageUrlTrait.php b/solid/lib/Controller/GetStorageUrlTrait.php
new file mode 100644
index 00000000..a5ab4a82
--- /dev/null
+++ b/solid/lib/Controller/GetStorageUrlTrait.php
@@ -0,0 +1,91 @@
+config = $config;
+ }
+
+ final public function setUrlGenerator(IURLGenerator $urlGenerator): void
+ {
+ $this->urlGenerator = $urlGenerator;
+ }
+
+ ////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\
+
+ protected ServerConfig $config;
+ protected IURLGenerator $urlGenerator;
+
+ /////////////////////////////// PROTECTED API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+
+ /**
+ * @FIXME: Add check for bob.nextcloud.local/solid/alice to throw 404
+ * @TODO: Use route without `@alice` in /apps/solid/@alice/profile/card#me when user-domains are enabled
+ */
+ public function getStorageUrl($userId) {
+ $routeUrl = $this->urlGenerator->linkToRoute(
+ 'solid.storage.handleHead',
+ ['userId' => $userId, 'path' => 'foo']
+ );
+
+ $storageUrl = $this->urlGenerator->getAbsoluteURL($routeUrl);
+
+ // (?) $storageUrl = preg_replace('/foo$/', '', $storageUrl);
+ $storageUrl = preg_replace('/foo$/', '/', $storageUrl);
+
+ if ($this->config->getUserSubDomainsEnabled()) {
+ $url = parse_url($storageUrl);
+
+ if (strpos($url['host'], $userId . '.') !== false) {
+ $url['host'] = str_replace($userId . '.', '', $url['host']);
+ }
+
+ $url['host'] = $userId . '.' . $url['host']; // $storageUrl = $userId . '.' . $storageUrl;
+ $storageUrl = $this->buildUrl($url);
+ }
+
+ return $storageUrl;
+ }
+
+ public function validateUrl(RequestInterface $request): bool {
+ $isValid = false;
+
+ $host = $request->getUri()->getHost();
+ $path = $request->getUri()->getPath();
+ $pathParts = explode('/', $path);
+
+ $pathUsers = array_filter($pathParts, static function ($value) {
+ return str_starts_with($value, '@');
+ });
+
+ if (count($pathUsers) === 1) {
+ $pathUser = reset($pathUsers);
+ $subDomainUser = explode('.', $host)[0];
+
+ $isValid = $pathUser === '@' . $subDomainUser;
+ }
+
+ return $isValid;
+ }
+
+ ////////////////////////////// UTILITY METHODS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+
+ private function buildUrl(array $parts) {
+ // @FIXME: Replace with existing more robust URL builder
+ return (isset($parts['scheme']) ? "{$parts['scheme']}:" : '') .
+ (isset($parts['host']) ? "//{$parts['host']}" : '') .
+ (isset($parts['port']) ? ":{$parts['port']}" : '') .
+ (isset($parts['path']) ? "{$parts['path']}" : '') .
+ (isset($parts['query']) ? "?{$parts['query']}" : '') .
+ (isset($parts['fragment']) ? "#{$parts['fragment']}" : '');
+ }
+}
diff --git a/solid/lib/Controller/ProfileController.php b/solid/lib/Controller/ProfileController.php
index 578e6239..287d87fc 100644
--- a/solid/lib/Controller/ProfileController.php
+++ b/solid/lib/Controller/ProfileController.php
@@ -4,6 +4,7 @@
use OCA\Solid\DpopFactoryTrait;
use OCA\Solid\PlainResponse;
use OCA\Solid\Notifications\SolidNotifications;
+use OCA\Solid\ServerConfig;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
@@ -21,13 +22,14 @@
class ProfileController extends Controller {
use DpopFactoryTrait;
+ use GetStorageUrlTrait;
- /* @var IURLGenerator */
- private $urlGenerator;
+ protected ServerConfig $config;
+ protected IURLGenerator $urlGenerator;
/* @var ISession */
private $session;
-
+
public function __construct(
$AppName,
IRequest $request,
@@ -82,7 +84,7 @@ private function getFileSystem($userId) {
$filesystem = new \League\Flysystem\Filesystem($rdfAdapter);
$filesystem->addPlugin(new \Pdsinterop\Rdf\Flysystem\Plugin\AsMime($formats));
-
+
$plugin = new \Pdsinterop\Rdf\Flysystem\Plugin\ReadRdf($graph);
$filesystem->addPlugin($plugin);
@@ -102,7 +104,7 @@ private function generateDefaultAcl($userId) {
acl:accessTo <./>;
acl:default <./>;
acl:mode acl:Read.
-
+
# The owner has full access to every resource in their pod.
# Other agents have no access rights,
# unless specifically authorized in other .acl resources.
@@ -131,11 +133,6 @@ private function getProfileUrl($userId) {
$profileUrl = preg_replace('/foo$/', '', $profileUrl);
return $profileUrl;
}
- private function getStorageUrl($userId) {
- $storageUrl = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("solid.storage.handleHead", array("userId" => $userId, "path" => "foo")));
- $storageUrl = preg_replace('/foo$/', '/', $storageUrl);
- return $storageUrl;
- }
/**
* @PublicPage
@@ -150,11 +147,11 @@ public function handleRequest($userId, $path) {
$this->filesystem = $this->getFileSystem($userId);
- $this->resourceServer = new ResourceServer($this->filesystem, $this->response);
+ $this->resourceServer = new ResourceServer($this->filesystem, $this->response);
$this->WAC = new WAC($this->filesystem);
$request = $this->rawRequest;
- $baseUrl = $this->getProfileUrl($userId);
+ $baseUrl = $this->getProfileUrl($userId);
$this->resourceServer->setBaseUrl($baseUrl);
$this->WAC->setBaseUrl($baseUrl);
$notifications = new SolidNotifications();
@@ -179,20 +176,21 @@ public function handleRequest($userId, $path) {
return $this->respond($response);
}
- $response = $this->resourceServer->respondToRequest($request);
+ $response = $this->resourceServer->respondToRequest($request);
$response = $this->WAC->addWACHeaders($request, $response, $webId);
return $this->respond($response);
}
-
+
/**
* @PublicPage
* @NoAdminRequired
* @NoCSRFRequired
*/
- public function handleGet($userId, $path) {
+ public function handleGet($userId, $path) {
+ //TODO: check that the $userId matches the userDomain, if enabled.
return $this->handleRequest($userId, $path);
}
-
+
/**
* @PublicPage
* @NoAdminRequired
@@ -206,20 +204,30 @@ public function handlePost($userId, $path) {
* @NoAdminRequired
* @NoCSRFRequired
*/
- public function handlePut() { // $userId, $path) {
- // FIXME: Adding the correct variables in the function name will make nextcloud
- // throw an error about accessing put twice, so we will find out the userId and path from $_SERVER instead;
-
- // because we got here, the request uri should look like:
- // /index.php/apps/solid/@{userId}/storage{path}
- $pathInfo = explode("@", $_SERVER['REQUEST_URI']);
- $pathInfo = explode("/", $pathInfo[1], 2);
- $userId = $pathInfo[0];
- $path = $pathInfo[1];
- $path = preg_replace("/^profile/", "", $path);
-
- return $this->handleRequest($userId, $path);
- }
+ public function handlePut() { // $userId, $path) {
+ // FIXME: Adding the correct variables in the function name will make nextcloud
+ // throw an error about accessing put twice, so we will find out the userId and path from $_SERVER instead;
+
+ // because we got here, the request uri should look like:
+ // - if we have user subdomains enabled:
+ // /index.php/apps/solid/profile{path}
+ // and otherwise:
+ // index.php/apps/solid/~{userId}/profile{path}
+ // In the first case, we'll get the username from the SERVER_NAME. In the latter, it will come from the URL;
+ if ($this->config->getUserSubDomainsEnabled()) {
+ $pathInfo = explode("profile/", $_SERVER['REQUEST_URI']);
+ $path = $pathInfo[1];
+ $userId = explode(".", $_SERVER['SERVER_NAME'])[0];
+ } else {
+ $pathInfo = explode("~", $_SERVER['REQUEST_URI']);
+ $pathInfo = explode("/", $pathInfo[1], 2);
+ $userId = $pathInfo[0];
+ $path = $pathInfo[1];
+ $path = preg_replace("/^profile/", "", $path);
+ }
+
+ return $this->handleRequest($userId, $path);
+ }
/**
* @PublicPage
* @NoAdminRequired
@@ -287,6 +295,7 @@ private function getUserProfile($userId) {
}
}
}
+ //TODO: privateTypeIndex and publisTypeIndex need to user getStorageURL
if ($user !== null) {
$profile = array(
'id' => $userId,
@@ -322,9 +331,9 @@ private function generateTurtleProfile($userId) {
@prefix inbox: <>.
@prefix sp: .
@prefix ser: <>.
-
+
pro:card a foaf:PersonalProfileDocument; foaf:maker :me; foaf:primaryTopic :me.
-
+
:me
a schem:Person, foaf:Person;
ldp:inbox inbox:;
diff --git a/solid/lib/Controller/SolidWebhookController.php b/solid/lib/Controller/SolidWebhookController.php
index 6a88a81e..5846097d 100644
--- a/solid/lib/Controller/SolidWebhookController.php
+++ b/solid/lib/Controller/SolidWebhookController.php
@@ -3,42 +3,35 @@
namespace OCA\Solid\Controller;
use Closure;
-use OCA\Solid\AppInfo\Application;
-use OCA\Solid\Service\SolidWebhookService;
-use OCA\Solid\ServerConfig;
-use OCA\Solid\PlainResponse;
-use OCA\Solid\Notifications\SolidNotifications;
+
use OCA\Solid\DpopFactoryTrait;
+use OCA\Solid\PlainResponse;
+use OCA\Solid\ServerConfig;
+use OCA\Solid\Service\SolidWebhookService;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
+use OCP\Files\IRootFolder;
+use OCP\IConfig;
+use OCP\IDBConnection;
use OCP\IRequest;
-use OCP\IUserManager;
-use OCP\IURLGenerator;
use OCP\ISession;
-use OCP\IDBConnection;
-use OCP\IConfig;
-use OCP\Files\IRootFolder;
-use OCP\Files\IHomeStorage;
-use OCP\Files\SimpleFS\ISimpleRoot;
-use OCP\AppFramework\Http;
-use OCP\AppFramework\Http\Response;
-use OCP\AppFramework\Http\JSONResponse;
-use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OCP\IURLGenerator;
+use OCP\IUserManager;
-use Pdsinterop\Solid\Resources\Server as ResourceServer;
-use Pdsinterop\Solid\Auth\Utils\DPop as DPop;
use Pdsinterop\Solid\Auth\WAC as WAC;
class SolidWebhookController extends Controller {
use DpopFactoryTrait;
+ use GetStorageUrlTrait;
- /* @var IURLGenerator */
- private $urlGenerator;
+ protected ServerConfig $config;
+ protected IURLGenerator $urlGenerator;
/* @var ISession */
private $session;
-
+
/** @var SolidWebhookService */
private $webhookService;
@@ -139,18 +132,13 @@ private function getFileSystem() {
$filesystem = new \League\Flysystem\Filesystem($rdfAdapter);
$filesystem->addPlugin(new \Pdsinterop\Rdf\Flysystem\Plugin\AsMime($formats));
-
+
$plugin = new \Pdsinterop\Rdf\Flysystem\Plugin\ReadRdf($graph);
$filesystem->addPlugin($plugin);
return $filesystem;
}
- private function getStorageUrl($userId) {
- $storageUrl = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("solid.storage.handleHead", array("userId" => $userId, "path" => "foo")));
- $storageUrl = preg_replace('/foo$/', '', $storageUrl);
- return $storageUrl;
- }
private function getAppBaseUrl() {
$appBaseUrl = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("solid.app.appLauncher"));
return $appBaseUrl;
@@ -162,20 +150,20 @@ private function initializeStorage($userId) {
}
private function parseTopic($topic) {
- // topic = https://nextcloud.server/solid/@alice/storage/foo/bar
+ // topic = https://nextcloud.server/solid/~alice/storage/foo/bar
$appBaseUrl = $this->getAppBaseUrl(); // https://nextcloud.server/solid/
- $internalUrl = str_replace($appBaseUrl, '', $topic); // @alice/storage/foo/bar
+ $internalUrl = str_replace($appBaseUrl, '', $topic); // ~alice/storage/foo/bar
$pathicles = explode("/", $internalUrl);
- $userId = $pathicles[0]; // @alice
- $userId = preg_replace("/^@/", "", $userId); // alice
- $storageUrl = $this->getStorageUrl($userId); // https://nextcloud.server/solid/@alice/storage/
+ $userId = $pathicles[0]; // ~alice
+ $userId = preg_replace("/^~/", "", $userId); // alice
+ $storageUrl = $this->getStorageUrl($userId); // https://nextcloud.server/solid/~alice/storage/
$storagePath = str_replace($storageUrl, '/', $topic); // /foo/bar
return array(
"userId" => $userId,
"path" => $storagePath
);
}
-
+
private function createGetRequest($topic) {
$serverParams = [];
$fileParams = [];
@@ -192,15 +180,15 @@ private function createGetRequest($topic) {
$headers
);
}
-
+
private function checkReadAccess($topic) {
- // split out $topic into $userId and $path https://nextcloud.server/solid/@alice/storage/foo/bar
+ // split out $topic into $userId and $path https://nextcloud.server/solid/~alice/storage/foo/bar
// - userId in this case is the pod owner (not the one doing the request). (alice)
// - path is the path within the storage pod (/foo/bar)
$target = $this->parseTopic($topic);
$userId = $target["userId"];
$path = $target["path"];
-
+
$this->initializeStorage($userId);
$this->WAC = new WAC($this->filesystem);
diff --git a/solid/lib/Controller/StorageController.php b/solid/lib/Controller/StorageController.php
index c5a66735..ed1b95d9 100644
--- a/solid/lib/Controller/StorageController.php
+++ b/solid/lib/Controller/StorageController.php
@@ -1,13 +1,15 @@
addPlugin(new \Pdsinterop\Rdf\Flysystem\Plugin\AsMime($formats));
-
+
$plugin = new \Pdsinterop\Rdf\Flysystem\Plugin\ReadRdf($graph);
$filesystem->addPlugin($plugin);
@@ -90,11 +91,7 @@ private function getFileSystem() {
private function getUserProfile($userId) {
return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("solid.profile.handleGet", array("userId" => $userId, "path" => "/card"))) . "#me";
}
- private function getStorageUrl($userId) {
- $storageUrl = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute("solid.storage.handleHead", array("userId" => $userId, "path" => "foo")));
- $storageUrl = preg_replace('/foo$/', '', $storageUrl);
- return $storageUrl;
- }
+
private function generateDefaultAcl($userId) {
$defaultAcl = <<< EOF
# Root ACL resource for the user account
@@ -303,7 +300,7 @@ public function handleRequest($userId, $path) {
$this->WAC = new WAC($this->filesystem);
$request = $this->rawRequest;
- $baseUrl = $this->getStorageUrl($userId);
+ $baseUrl = $this->getStorageUrl($userId);
$this->resourceServer->setBaseUrl($baseUrl);
$this->WAC->setBaseUrl($baseUrl);
@@ -351,20 +348,20 @@ public function handleRequest($userId, $path) {
->withStatus(403, "Access denied");
return $this->respond($response);
}
- $response = $this->resourceServer->respondToRequest($request);
+ $response = $this->resourceServer->respondToRequest($request);
$response = $this->WAC->addWACHeaders($request, $response, $webId);
return $this->respond($response);
}
-
+
/**
* @PublicPage
* @NoAdminRequired
* @NoCSRFRequired
*/
- public function handleGet($userId, $path) {
+ public function handleGet($userId, $path) {
return $this->handleRequest($userId, $path);
}
-
+
/**
* @PublicPage
* @NoAdminRequired
@@ -378,20 +375,29 @@ public function handlePost($userId, $path) {
* @NoAdminRequired
* @NoCSRFRequired
*/
- public function handlePut() { // $userId, $path) {
- // FIXME: Adding the correct variables in the function name will make nextcloud
- // throw an error about accessing put twice, so we will find out the userId and path from $_SERVER instead;
-
- // because we got here, the request uri should look like:
- // /index.php/apps/solid/@{userId}/storage{path}
- $pathInfo = explode("@", $_SERVER['REQUEST_URI']);
- $pathInfo = explode("/", $pathInfo[1], 2);
- $userId = $pathInfo[0];
- $path = $pathInfo[1];
- $path = preg_replace("/^storage/", "", $path);
-
- return $this->handleRequest($userId, $path);
- }
+ public function handlePut() { // $userId, $path) {
+ // FIXME: Adding the correct variables in the function name will make nextcloud
+ // throw an error about accessing put twice, so we will find out the userId and path from $_SERVER instead;
+ // because we got here, the request uri should look like:
+ // - if we have user subdomains enabled:
+ // /index.php/apps/solid/storage{path}
+ // and otherwise:
+ // index.php/apps/solid/~{userId}/storage{path}
+ // In the first case, we'll get the username from the SERVER_NAME. In the latter, it will come from the URL;
+ if ($this->config->getUserSubDomainsEnabled()) {
+ $pathInfo = explode("storage/", $_SERVER['REQUEST_URI']);
+ $path = $pathInfo[1];
+ $userId = explode(".", $_SERVER['SERVER_NAME'])[0];
+ } else {
+ $pathInfo = explode("~", $_SERVER['REQUEST_URI']);
+ $pathInfo = explode("/", $pathInfo[1], 2);
+ $userId = $pathInfo[0];
+ $path = $pathInfo[1];
+ $path = preg_replace("/^storage/", "", $path);
+ }
+
+ return $this->handleRequest($userId, $path);
+ }
/**
* @PublicPage
* @NoAdminRequired
@@ -434,7 +440,7 @@ private function respond($response) {
// $result->addHeader('Access-Control-Allow-Credentials', 'true');
// $result->addHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// $result->addHeader('Access-Control-Allow-Origin', $origin);
-
+
$policy = new EmptyContentSecurityPolicy();
$policy->addAllowedStyleDomain("*");
$policy->addAllowedStyleDomain("data:");
diff --git a/solid/lib/Sections/SolidAdmin.php b/solid/lib/Sections/SolidAdmin.php
index 59a5c9b1..1e66b429 100644
--- a/solid/lib/Sections/SolidAdmin.php
+++ b/solid/lib/Sections/SolidAdmin.php
@@ -29,4 +29,5 @@ public function getName(): string {
public function getPriority(): int {
return 98;
}
+
}
diff --git a/solid/lib/ServerConfig.php b/solid/lib/ServerConfig.php
index 8313b1f8..9874cb6c 100644
--- a/solid/lib/ServerConfig.php
+++ b/solid/lib/ServerConfig.php
@@ -10,7 +10,6 @@
* @package OCA\Solid
*/
class ServerConfig extends BaseServerConfig {
- private IConfig $config;
private IUrlGenerator $urlGenerator;
private IUserManager $userManager;
@@ -23,6 +22,7 @@ public function __construct(IConfig $config, IUrlGenerator $urlGenerator, IUserM
$this->config = $config;
$this->userManager = $userManager;
$this->urlGenerator = $urlGenerator;
+
parent::__construct($config);
}
diff --git a/solid/lib/Settings/SolidAdmin.php b/solid/lib/Settings/SolidAdmin.php
index 2fc684f8..a4d4e73d 100644
--- a/solid/lib/Settings/SolidAdmin.php
+++ b/solid/lib/Settings/SolidAdmin.php
@@ -25,9 +25,10 @@ public function getForm() {
$allClients = $this->serverConfig->getClients();
$parameters = [
- 'privateKey' => $this->serverConfig->getPrivateKey(),
- 'encryptionKey' => $this->serverConfig->getEncryptionKey(),
- 'clients' => $allClients
+ 'clients' => $allClients,
+ 'encryptionKey' => $this->serverConfig->getEncryptionKey(),
+ 'privateKey' => $this->serverConfig->getPrivateKey(),
+ 'userSubDomainsEnabled' => $this->serverConfig->getUserSubDomainsEnabled(),
];
return new TemplateResponse('solid', 'admin', $parameters, '');
diff --git a/solid/templates/admin.php b/solid/templates/admin.php
index c414498c..616f7edb 100644
--- a/solid/templates/admin.php
+++ b/solid/templates/admin.php
@@ -1,19 +1,37 @@
-
t('Solid OpenID Connect Settings')); ?>
+
t('Solid Server Settings')); ?>
+
+
+
t('Solid OpenID Connect Settings')); ?>
+
+
-
\ No newline at end of file
diff --git a/solid/templates/applauncher/index.php b/solid/templates/applauncher/index.php
index d6fc1d65..bfd38195 100644
--- a/solid/templates/applauncher/index.php
+++ b/solid/templates/applauncher/index.php
@@ -44,6 +44,14 @@
+
+ Permissions to the file in your Pod:
+
+
+
+
+
+
Permissions on in your Pod:
diff --git a/solid/tests/Unit/BaseServerConfigTest.php b/solid/tests/Unit/BaseServerConfigTest.php
new file mode 100644
index 00000000..aeb70bad
--- /dev/null
+++ b/solid/tests/Unit/BaseServerConfigTest.php
@@ -0,0 +1,105 @@
+expectException(TypeError::class);
+ $this->expectExceptionMessage('Too few arguments to function');
+
+ new BaseServerConfig();
+ }
+
+ /**
+ * @testdox BaseServerConfig should be instantiated when given a valid Configuration
+ * @covers ::__construct
+ */
+ public function testConstructorWithValidConfig()
+ {
+ $configMock = $this->createMock(IConfig::class);
+
+ $baseServerConfig = new BaseServerConfig($configMock);
+
+ $this->assertInstanceOf(BaseServerConfig::class, $baseServerConfig);
+ }
+
+ /**
+ * @testdox BaseServerConfig should return a boolean when asked whether UserSubDomains are Enabled
+ * @covers ::getUserSubDomainsEnabled
+ * @dataProvider provideBooleans
+ */
+ public function testGetUserSubDomainsEnabled($expected)
+ {
+ $configMock = $this->createMock(IConfig::class);
+ $configMock->method('getAppValue')->willReturn($expected);
+
+ $baseServerConfig = new BaseServerConfig($configMock);
+ $actual = $baseServerConfig->getUserSubDomainsEnabled();
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @testdox BaseServerConfig should get value from AppConfig when asked whether UserSubDomains are Enabled
+ * @covers ::getUserSubDomainsEnabled
+ */
+ public function testGetUserSubDomainsEnabledFromAppConfig()
+ {
+ $configMock = $this->createMock(IConfig::class);
+ $configMock->expects($this->atLeast(1))
+ ->method('getAppValue')
+ ->with(Application::APP_ID, 'userSubDomainsEnabled', false)
+ ->willReturn(true);
+
+ $baseServerConfig = new BaseServerConfig($configMock);
+ $actual = $baseServerConfig->getUserSubDomainsEnabled();
+
+ $this->assertTrue($actual);
+ }
+
+ /**
+ * @testdox BaseServerConfig should set value in AppConfig when asked to set UserSubDomainsEnabled
+ * @covers ::setUserSubDomainsEnabled
+ *
+ * @dataProvider provideBooleans
+ */
+ public function testSetUserSubDomainsEnabled($expected)
+ {
+ $configMock = $this->createMock(IConfig::class);
+ $configMock->expects($this->atLeast(1))
+ ->method('setAppValue')
+ ->with(Application::APP_ID, 'userSubDomainsEnabled', $expected)
+ ;
+
+ $baseServerConfig = new BaseServerConfig($configMock);
+ $baseServerConfig->setUserSubDomainsEnabled($expected);
+ }
+
+ /////////////////////////////// DATAPROVIDERS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+
+ public function provideBooleans()
+ {
+ return [
+ 'false' => [false],
+ 'true' => [true],
+ ];
+ }
+}
diff --git a/solid/tests/Unit/Controller/GetStorageUrlTraitTest.php b/solid/tests/Unit/Controller/GetStorageUrlTraitTest.php
new file mode 100644
index 00000000..34c73f5c
--- /dev/null
+++ b/solid/tests/Unit/Controller/GetStorageUrlTraitTest.php
@@ -0,0 +1,190 @@
+trait = new class {
+ use GetStorageUrlTrait;
+ };
+ }
+
+ /////////////////////////////////// TESTS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+
+ /**
+ * @testdox GetStorageUrlTrait should complain when called before given a UrlGenerator
+ * @covers ::getStorageUrl
+ */
+ public function testGetStorageUrlWithoutUrlGenerator()
+ {
+ $this->expectException(Error::class);
+ $this->expectExceptionMessage('urlGenerator must not be accessed before initialization');
+
+ $this->trait->getStorageUrl(self::MOCK_USER_ID);
+ }
+
+ /**
+ * @testdox GetStorageUrlTrait should complain when called before given a Configuration
+ * @covers ::getStorageUrl
+ */
+ public function testGetStorageUrlWithoutConfig()
+ {
+ $mockUrlGenerator = $this->getMockUrlGenerator(self::MOCK_URL);
+
+ $this->expectException(Error::class);
+ $this->expectExceptionMessage('config must not be accessed before initialization');
+
+ $this->trait->setUrlGenerator($mockUrlGenerator);
+
+ $this->trait->getStorageUrl(self::MOCK_USER_ID);
+ }
+
+ /**
+ * @testdox GetStorageUrlTrait should return a string when called with a UrlGenerator and Configuration
+ * @covers ::getStorageUrl
+ * @dataProvider provideSubDomainsDisabledUrls
+ */
+ public function testGetStorageUrlWithUserSubDomainsDisabled($url, $userId, $expected)
+ {
+ $mockConfig = $this->getMockConfig();
+ $mockUrlGenerator = $this->getMockUrlGenerator($url);
+
+ $this->trait->setUrlGenerator($mockUrlGenerator);
+ $this->trait->setConfig($mockConfig);
+
+ $actual = $this->trait->getStorageUrl($userId);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @testdox GetStorageUrlTrait should return a string when called with a UrlGenerator and Configuration
+ * @covers ::getStorageUrl
+ * @covers ::buildUrl
+ *
+ * @dataProvider provideSubDomainsEnabledUrls
+ */
+ public function testGetStorageUrlWithUserSubDomainsEnabled($url, $userId, $expected)
+ {
+ $mockUrlGenerator = $this->getMockUrlGenerator($url);
+ $mockConfig = $this->getMockConfig(true);
+
+ $this->trait->setUrlGenerator($mockUrlGenerator);
+ $this->trait->setConfig($mockConfig);
+
+ $actual = $this->trait->getStorageUrl($userId);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @testdox GetStorageUrlTrait should return expected validity when asked to validateUrl
+ *
+ * @covers ::validateUrl
+ *
+ * @dataProvider provideRequests
+ */
+ public function testValidateUrl(RequestInterface $response, $expected)
+ {
+ $actual = $this->trait->validateUrl($response);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ ////////////////////////////// MOCKS AND STUBS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+
+ public function getMockConfig($enabled = false): MockObject|ServerConfig
+ {
+ $mockConfig = $this->getMockBuilder(ServerConfig::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $mockConfig->expects($this->any())
+ ->method('getUserSubDomainsEnabled')
+ ->willReturn($enabled);
+
+ return $mockConfig;
+ }
+
+ public function getMockUrlGenerator($url): MockObject|IURLGenerator
+ {
+ $mockUrlGenerator = $this
+ ->getMockBuilder(IURLGenerator::class)
+ ->getMock();
+
+ $mockUrlGenerator->expects($this->atLeast(1))
+ ->method('getAbsoluteURL')
+ ->willReturn($url);
+
+ return $mockUrlGenerator;
+ }
+
+ /////////////////////////////// DATAPROVIDERS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+
+ public function provideRequests()
+ {
+ $request = new Request();
+
+ return [
+ 'invalid: invalid URL' => ['request' => $request->withUri(new Uri('!@#$%^&*()_')), 'expected' => false],
+ 'invalid: no domain user' => ['request' => $request->withUri(new Uri('https://example.com/@alice/profile/card#me')), 'expected' => false],
+ 'invalid: no path or domain user' => ['request' => $request->withUri(new Uri('https://example.com/')), 'expected' => false],
+ 'invalid: no path user' => ['request' => $request->withUri(new Uri('https://alice.example.com/profile/card#me')), 'expected' => false],
+ 'invalid: no URL' => ['request' => $request, 'expected' => false],
+ 'invalid: path and domain user mismatch' => ['request' => $request->withUri(new Uri('https://bob.example.com/@alice/profile/card#me')), 'expected' => false],
+ 'valid: minimal path and domain user match' => ['request' => $request->withUri(new Uri('https://alice.example.com/apps/@alice')), 'expected' => true],
+ 'valid: path and domain user match' => ['request' => $request->withUri(new Uri('https://alice.example.com/apps/solid/@alice/profile/card#me')), 'expected' => true],
+ ];
+ }
+
+ public function provideSubDomainsDisabledUrls()
+ {
+ return [
+ ['url' => 'example.com/foo', 'userId' => 'alice', 'expected' => 'example.com//'],
+ ['url' => 'https://example.com/foo', 'userId' => 'alice', 'expected' => 'https://example.com//'],
+ ['url' => 'http://example.com/foo', 'userId' => 'alice', 'expected' => 'http://example.com//'],
+ ['url' => 'https://bob.example.com/foo', 'userId' => 'alice', 'expected' => 'https://bob.example.com//'],
+ ['url' => 'http://bob.example.com/foo', 'userId' => 'alice', 'expected' => 'http://bob.example.com//'],
+ ['url' => 'https://bob.example.com/foo', 'userId' => 'bob', 'expected' => 'https://bob.example.com//'],
+ ['url' => 'http://bob.example.com/foo', 'userId' => 'bob', 'expected' => 'http://bob.example.com//'],
+ ];
+ }
+
+ public function provideSubDomainsEnabledUrls()
+ {
+ return [
+ // @FIXME: "Undefined array key 'host'" caused by the use of `parse_url`
+ // ['url' => 'example.com/foo', 'userId' => 'alice', 'expected' => 'example.com//'],
+
+ ['url' => 'https://example.com/foo', 'userId' => 'alice', 'expected' => 'https://alice.example.com//'],
+ ['url' => 'http://example.com/foo', 'userId' => 'alice', 'expected' => 'http://alice.example.com//'],
+ ['url' => 'https://bob.example.com/foo', 'userId' => 'alice', 'expected' => 'https://alice.bob.example.com//'],
+ ['url' => 'http://bob.example.com/foo', 'userId' => 'alice', 'expected' => 'http://alice.bob.example.com//'],
+ ['url' => 'https://bob.example.com/foo', 'userId' => 'bob', 'expected' => 'https://bob.example.com//'],
+ ['url' => 'http://bob.example.com/foo', 'userId' => 'bob', 'expected' => 'http://bob.example.com//'],
+ ];
+ }
+}
diff --git a/solid/tests/bootstrap.php b/solid/tests/bootstrap.php
index 91a9a5b9..c8d9e138 100644
--- a/solid/tests/bootstrap.php
+++ b/solid/tests/bootstrap.php
@@ -6,7 +6,7 @@
// phpcs:enable
}
-require_once __DIR__.'/../../../lib/base.php';
+require_once __DIR__.'../vendor/nextcloud/server/lib/base.php';
// Fix for "Autoload path not allowed: .../tests/lib/testcase.php"
\OC::$loader->addValidRoot(OC::$SERVERROOT . '/tests');