Compare commits
No commits in common. "master" and "Release_2305.4" have entirely different histories.
master
...
Release_23
@ -21,4 +21,3 @@ RECAPTCHA_SITEKEY=your_recaptcha_sitekey
|
|||||||
RECAPTCHA_SECRET=your_recaptcha_secret
|
RECAPTCHA_SECRET=your_recaptcha_secret
|
||||||
JWT_RSA_PRIVATE_KEY=jwt-rsa256-private.pem
|
JWT_RSA_PRIVATE_KEY=jwt-rsa256-private.pem
|
||||||
JWT_RSA_PUBLIC_KEY=jwt-rsa256-public.pem
|
JWT_RSA_PUBLIC_KEY=jwt-rsa256-public.pem
|
||||||
JWT_KEY_KID=1
|
|
||||||
|
65
Jenkinsfile
vendored
65
Jenkinsfile
vendored
@ -13,9 +13,8 @@ pipeline {
|
|||||||
}
|
}
|
||||||
agent {
|
agent {
|
||||||
dockerfile {
|
dockerfile {
|
||||||
filename 'docker/Dockerfile'
|
filename 'docker/Dockerfile-test'
|
||||||
dir '.'
|
dir '.'
|
||||||
additionalBuildArgs '--target rvr_base'
|
|
||||||
reuseNode true
|
reuseNode true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -27,9 +26,8 @@ pipeline {
|
|||||||
stage('Unit Testing') {
|
stage('Unit Testing') {
|
||||||
agent {
|
agent {
|
||||||
dockerfile {
|
dockerfile {
|
||||||
filename 'docker/Dockerfile'
|
filename 'docker/Dockerfile-test'
|
||||||
dir '.'
|
dir '.'
|
||||||
additionalBuildArgs '--target rvr_base'
|
|
||||||
reuseNode true
|
reuseNode true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,7 +35,7 @@ pipeline {
|
|||||||
sh 'vendor/bin/phpunit --log-junit unit_test_results.xml --testdox tests'
|
sh 'vendor/bin/phpunit --log-junit unit_test_results.xml --testdox tests'
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
always {
|
success {
|
||||||
archiveArtifacts 'unit_test_results.xml'
|
archiveArtifacts 'unit_test_results.xml'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,9 +44,8 @@ pipeline {
|
|||||||
stage('Static Code Analysis') {
|
stage('Static Code Analysis') {
|
||||||
agent {
|
agent {
|
||||||
dockerfile {
|
dockerfile {
|
||||||
filename 'docker/Dockerfile'
|
filename 'docker/Dockerfile-test'
|
||||||
dir '.'
|
dir '.'
|
||||||
additionalBuildArgs '--target rvr_base'
|
|
||||||
reuseNode true
|
reuseNode true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,62 +53,10 @@ pipeline {
|
|||||||
sh 'php vendor/bin/phpstan analyse -c phpstan.neon --error-format=prettyJson > static_code_analysis_results.json'
|
sh 'php vendor/bin/phpstan analyse -c phpstan.neon --error-format=prettyJson > static_code_analysis_results.json'
|
||||||
}
|
}
|
||||||
post {
|
post {
|
||||||
always {
|
success {
|
||||||
archiveArtifacts 'static_code_analysis_results.json'
|
archiveArtifacts 'static_code_analysis_results.json'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage('Prepare Docker release') {
|
|
||||||
environment {
|
|
||||||
COMPOSER_HOME="${WORKSPACE}/.composer"
|
|
||||||
npm_config_cache="${WORKSPACE}/.npm"
|
|
||||||
}
|
|
||||||
agent {
|
|
||||||
dockerfile {
|
|
||||||
filename 'docker/Dockerfile'
|
|
||||||
dir '.'
|
|
||||||
additionalBuildArgs '--target rvr_base'
|
|
||||||
reuseNode true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
sh script: 'git clean -ffdx', label: 'Clean repository'
|
|
||||||
env.VERSION = sh(script: 'git describe --tags --always --match "Release_*" HEAD', returnStdout: true).trim()
|
|
||||||
sh script: 'docker/scripts/release.sh', label: 'Release script'
|
|
||||||
sh script: "rm -rf ${env.COMPOSER_HOME} ${env.npm_config_cache}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Release Docker image') {
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
withDockerRegistry([credentialsId: 'gitea-system-user', url: 'https://git.esoko.eu/']) {
|
|
||||||
sh script: 'docker buildx create --use --bootstrap --platform=linux/arm64,linux/amd64 --name multi-platform-builder'
|
|
||||||
sh script: """docker buildx build \
|
|
||||||
--platform linux/amd64,linux/arm64 \
|
|
||||||
-f docker/Dockerfile \
|
|
||||||
--target rvr_release \
|
|
||||||
-t git.esoko.eu/esoko/rvr:${env.VERSION} \
|
|
||||||
--push \
|
|
||||||
.""",
|
|
||||||
label: 'Build Docker image'
|
|
||||||
|
|
||||||
if (env.BRANCH_NAME == 'master') {
|
|
||||||
if (env.VERSION ==~ '.*-\\d+-g[a-f0-9]{7}') {
|
|
||||||
env.FIXED_VERSION = 'dev'
|
|
||||||
} else {
|
|
||||||
env.FIXED_VERSION = 'stable'
|
|
||||||
}
|
|
||||||
sh script: """docker buildx imagetools create \
|
|
||||||
-t git.esoko.eu/esoko/rvr:${env.FIXED_VERSION} \
|
|
||||||
git.esoko.eu/esoko/rvr:${env.VERSION}"""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
118
README.md
118
README.md
@ -2,81 +2,81 @@
|
|||||||
|
|
||||||
[](https://ci.esoko.eu/job/rvr-nextgen/job/master/)
|
[](https://ci.esoko.eu/job/rvr-nextgen/job/master/)
|
||||||
|
|
||||||
This is the RVR Application project.
|
This is the RVR Application project. This is a game about guessing where you are based on a street view panorama - inspired by existing applications.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Set environment variables
|
### Clone the Git repository
|
||||||
|
|
||||||
The `.env` file contains several environment variables that are needed by the application to work properly. These should be configured for your environment. Check `.env.example` for reference.
|
The first step is obviously cloning the repository to your machine:
|
||||||
|
|
||||||
**Important: `DEV` should NOT be set for production! See section Development if you want to use the application in development mode.**
|
|
||||||
|
|
||||||
### Docker Compose
|
|
||||||
|
|
||||||
Create a `docker-compose.yml` file. The example code below assumes that `.env` is placed in the same folder.
|
|
||||||
|
|
||||||
```yml
|
|
||||||
version: '3'
|
|
||||||
services:
|
|
||||||
app:
|
|
||||||
image: git.esoko.eu/esoko/rvr:latest
|
|
||||||
depends_on:
|
|
||||||
mariadb:
|
|
||||||
condition: service_healthy
|
|
||||||
ports:
|
|
||||||
- 80:80
|
|
||||||
volumes:
|
|
||||||
- .env:/var/www/rvr/.env
|
|
||||||
mariadb:
|
|
||||||
image: mariadb:10.3
|
|
||||||
volumes:
|
|
||||||
- mysql:/var/lib/mysql
|
|
||||||
environment:
|
|
||||||
MYSQL_ROOT_PASSWORD: 'root'
|
|
||||||
MYSQL_DATABASE: 'rvr'
|
|
||||||
MYSQL_USER: 'rvr'
|
|
||||||
MYSQL_PASSWORD: 'rvr'
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "mysqladmin -u $$MYSQL_USER -p$$MYSQL_PASSWORD ping -h localhost || exit 1"]
|
|
||||||
start_period: 5s
|
|
||||||
start_interval: 1s
|
|
||||||
interval: 5s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
volumes:
|
|
||||||
mysql:
|
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://git.esoko.eu/esoko/rvr-nextgen.git
|
||||||
```
|
```
|
||||||
|
|
||||||
Execute the following command:
|
All the commands listed here should be executed from the repository root.
|
||||||
```bash
|
|
||||||
|
### Setup Docker stack (recommended)
|
||||||
|
|
||||||
|
The easiest way to build up a fully working application with web server and database is to use Docker Compose with the included `docker-compose.yml`.
|
||||||
|
|
||||||
|
All you have to do is executing the following command:
|
||||||
|
|
||||||
|
```
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Attach shell to the container of `rvr-nextgen-app`:
|
||||||
|
|
||||||
**And you are done!** The application is ready to use. You can create the first administrative user with the following command after attaching to the `app` container:
|
```
|
||||||
|
docker exec -it rvr-nextgen-app bash
|
||||||
|
```
|
||||||
|
|
||||||
|
All of the following commands should be executed there.
|
||||||
|
|
||||||
|
### Manual setup (alternative)
|
||||||
|
|
||||||
|
If you don't use the Docker stack you need to install your environment manually. Check `docker-compose.yml` and `docker/Dockerfile` to see the system requirements.
|
||||||
|
|
||||||
|
### Initialize project
|
||||||
|
|
||||||
|
This command installes all of the Composer requirements and creates a copy of the example `.env` file.
|
||||||
|
|
||||||
|
```
|
||||||
|
composer create-project
|
||||||
|
```
|
||||||
|
|
||||||
|
### Set environment variables
|
||||||
|
|
||||||
|
The `.env` file contains several environment variables that are needed by the application to work properly. These should be configured for your environment.
|
||||||
|
|
||||||
|
One very important variable is `DEV`. This indicates that the application operates in development (staging) and not in production mode.
|
||||||
|
|
||||||
|
**Hint:** If you install the application in the Docker stack for development (staging) environment, only the variables for external dependencies (API keys, map attribution, etc.) should be adapted. All other variables (for DB connection, static root, mailing, multiplayer, etc.) are fine with the default value.
|
||||||
|
|
||||||
|
### (Production only) Create cron job
|
||||||
|
|
||||||
|
To maintain database (delete inactive users, old sessions etc.), the command `db:maintain` should be regularly executed. It is recommended to create a cron job that runs every hour:
|
||||||
|
|
||||||
|
```
|
||||||
|
0 * * * * /path/to/your/installation/rvr db:maintain >>/var/log/cron-rvr.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Finalize installation
|
||||||
|
|
||||||
|
After you followed the above steps, execute the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
scripts/install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**And you are done!** The application is ready to use and develop. In development mode an administrative user is also created by the installation script, email is **rvr@rvr.dev**, password is **123456**. In production mode you should create the first administrative user with the following command:
|
||||||
|
|
||||||
```
|
```
|
||||||
./rvr user:add EMAIL PASSWORD admin
|
./rvr user:add EMAIL PASSWORD admin
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development
|
If you installed it in the Docker stack, you can reach it on http://localhost. The mails that are sent by the application can be found on http://localhost:8080/. If needed, the database server can be directly reached on localhost:3306.
|
||||||
|
|
||||||
### Set environment variables
|
|
||||||
|
|
||||||
`.env.example` should be copied to `.env` into the repo root. Only the variables for external dependencies (API keys, map attribution, etc.) should be adapted. All other variables (for DB connection, static root, mailing, multiplayer, etc.) are fine with the default value. **`DEV=1` should be set for development!**
|
|
||||||
|
|
||||||
### Docker Compose
|
|
||||||
|
|
||||||
Execute the following command from the repo root:
|
|
||||||
```bash
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
**And you are done!** You can reach the application on http://localhost. The mails that are sent by the application can be found on http://localhost:8080. If needed, the database server can be directly reached on localhost:3306, or you can use Adminer web interface on http://localhost:9090
|
|
||||||
|
|
||||||
You might have to attach to the `app` container, e.g. for creating users, `composer update`, etc.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -10,11 +10,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"esoko/soko-web": "0.15",
|
"esoko/soko-web": "0.10",
|
||||||
"firebase/php-jwt": "^6.4"
|
"firebase/php-jwt": "^6.4"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^10.3",
|
"phpunit/phpunit": "^9.6",
|
||||||
"phpstan/phpstan": "^1.10"
|
"phpstan/phpstan": "^1.10"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
1031
composer.lock
generated
1031
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,12 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use RVR\PersistentData\Model\Community;
|
|
||||||
use SokoWeb\Database\Query\Select;
|
|
||||||
|
|
||||||
$select = new Select(Container::$dbConnection);
|
|
||||||
$communities = Container::$persistentDataManager->selectMultipleFromDb($select, Community::class);
|
|
||||||
|
|
||||||
foreach ($communities as $community) {
|
|
||||||
$community->generateSlug();
|
|
||||||
Container::$persistentDataManager->saveToDb($community);
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE `communities`
|
|
||||||
ADD `slug` varchar(255) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL AFTER `id`,
|
|
||||||
ADD UNIQUE `slug` (`slug`);
|
|
@ -1,13 +0,0 @@
|
|||||||
CREATE TABLE `events` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`slug` varchar(255) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
|
|
||||||
`community_id` int(10) unsigned NOT NULL,
|
|
||||||
`start` timestamp NOT NULL DEFAULT current_timestamp(),
|
|
||||||
`end` timestamp NOT NULL DEFAULT current_timestamp(),
|
|
||||||
`title` varchar(255) NOT NULL,
|
|
||||||
`description` text NOT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `community_id` (`community_id`),
|
|
||||||
UNIQUE KEY `slug` (`slug`),
|
|
||||||
CONSTRAINT `events_community_id` FOREIGN KEY (`community_id`) REFERENCES `communities` (`id`)
|
|
||||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
|
@ -1,4 +0,0 @@
|
|||||||
ALTER TABLE `transactions`
|
|
||||||
ADD `event_id` int(10) unsigned NULL AFTER `community_id`,
|
|
||||||
ADD KEY `event_id` (`event_id`),
|
|
||||||
ADD CONSTRAINT `transactions_event_id` FOREIGN KEY (`event_id`) REFERENCES `events` (`id`);
|
|
@ -1,11 +0,0 @@
|
|||||||
CREATE TABLE `transaction_payees` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`transaction_id` int(10) unsigned NOT NULL,
|
|
||||||
`user_id` int(10) unsigned DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `unique_user_for_transaction` (`transaction_id`, `user_id`),
|
|
||||||
KEY `transaction_id` (`transaction_id`),
|
|
||||||
KEY `user_id` (`user_id`),
|
|
||||||
CONSTRAINT `transaction_payees_transaction_id` FOREIGN KEY (`transaction_id`) REFERENCES `transactions` (`id`),
|
|
||||||
CONSTRAINT `transaction_payees_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
|
|
||||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
|
@ -1,26 +0,0 @@
|
|||||||
CREATE TABLE `oauth_sessions` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`client_id` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
|
||||||
`scope` varchar(255) NOT NULL DEFAULT '',
|
|
||||||
`nonce` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
|
||||||
`user_id` int(10) unsigned DEFAULT NULL,
|
|
||||||
`code` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
|
|
||||||
`code_challenge` varchar(128) NULL,
|
|
||||||
`code_challenge_method` enum('plain', 'S256') NULL,
|
|
||||||
`token_claimed` tinyint(1) NOT NULL DEFAULT 0,
|
|
||||||
`created` timestamp NOT NULL DEFAULT current_timestamp(),
|
|
||||||
`expires` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `code` (`code`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
|
||||||
|
|
||||||
ALTER TABLE `oauth_tokens`
|
|
||||||
ADD `session_id` int(10) unsigned NULL,
|
|
||||||
ADD CONSTRAINT `oauth_tokens_session_id` FOREIGN KEY (`session_id`) REFERENCES `oauth_sessions` (`id`),
|
|
||||||
DROP INDEX `code`,
|
|
||||||
DROP INDEX `access_token`,
|
|
||||||
DROP `scope`,
|
|
||||||
DROP `nonce`,
|
|
||||||
DROP `user_id`,
|
|
||||||
DROP `code`,
|
|
||||||
DROP `access_token`;
|
|
@ -2,17 +2,12 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: ./docker
|
||||||
dockerfile: docker/Dockerfile
|
dockerfile: Dockerfile-app
|
||||||
target: rvr_dev
|
|
||||||
depends_on:
|
|
||||||
mariadb:
|
|
||||||
condition: service_healthy
|
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
volumes:
|
volumes:
|
||||||
- .:/var/www/rvr
|
- .:/var/www/rvr
|
||||||
working_dir: /var/www/rvr
|
|
||||||
mariadb:
|
mariadb:
|
||||||
image: mariadb:10.3
|
image: mariadb:10.3
|
||||||
ports:
|
ports:
|
||||||
@ -24,13 +19,6 @@ services:
|
|||||||
MYSQL_DATABASE: 'rvr'
|
MYSQL_DATABASE: 'rvr'
|
||||||
MYSQL_USER: 'rvr'
|
MYSQL_USER: 'rvr'
|
||||||
MYSQL_PASSWORD: 'rvr'
|
MYSQL_PASSWORD: 'rvr'
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "mysqladmin -u $$MYSQL_USER -p$$MYSQL_PASSWORD ping -h localhost || exit 1"]
|
|
||||||
start_period: 5s
|
|
||||||
start_interval: 1s
|
|
||||||
interval: 5s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
adminer:
|
adminer:
|
||||||
image: adminer:4.8.1-standalone
|
image: adminer:4.8.1-standalone
|
||||||
ports:
|
ports:
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
FROM ubuntu:22.04 AS rvr_base
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
|
|
||||||
RUN apt update --fix-missing && apt install -y sudo curl git unzip mariadb-client nginx \
|
|
||||||
php-apcu php8.1-cli php8.1-curl php8.1-fpm php8.1-mbstring php8.1-mysql php8.1-zip php8.1-xml
|
|
||||||
|
|
||||||
RUN mkdir -p /run/php
|
|
||||||
COPY docker/configs/nginx.conf /etc/nginx/sites-available/default
|
|
||||||
|
|
||||||
COPY docker/scripts/install-composer.sh install-composer.sh
|
|
||||||
RUN ./install-composer.sh
|
|
||||||
|
|
||||||
COPY docker/scripts/install-nodejs.sh install-nodejs.sh
|
|
||||||
RUN ./install-nodejs.sh
|
|
||||||
RUN npm install -g uglify-js clean-css-cli svgo yarn
|
|
||||||
|
|
||||||
|
|
||||||
FROM rvr_base AS rvr_dev
|
|
||||||
|
|
||||||
RUN apt update --fix-missing && apt install -y php-xdebug
|
|
||||||
|
|
||||||
RUN echo "xdebug.remote_enable = 1" >> /etc/php/8.1/mods-available/xdebug.ini &&\
|
|
||||||
echo "xdebug.remote_autostart = 1" >> /etc/php/8.1/mods-available/xdebug.ini &&\
|
|
||||||
echo "xdebug.remote_connect_back = 1" >> /etc/php/8.1/mods-available/xdebug.ini
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
EXPOSE 5000
|
|
||||||
EXPOSE 8090
|
|
||||||
EXPOSE 9229
|
|
||||||
ENTRYPOINT docker/scripts/entry-point-dev.sh
|
|
||||||
|
|
||||||
|
|
||||||
FROM rvr_base AS rvr_release
|
|
||||||
|
|
||||||
RUN apt update --fix-missing && apt install -y cron
|
|
||||||
|
|
||||||
WORKDIR /var/www/rvr
|
|
||||||
COPY ./ /var/www/rvr
|
|
||||||
RUN rm -rf /var/www/rvr/.git
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
EXPOSE 8090
|
|
||||||
ENTRYPOINT docker/scripts/entry-point.sh
|
|
30
docker/Dockerfile-app
Normal file
30
docker/Dockerfile-app
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
FROM ubuntu:focal
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
|
# Install Nginx, PHP and further necessary packages
|
||||||
|
RUN apt update --fix-missing
|
||||||
|
RUN apt install -y curl git unzip mariadb-client nginx \
|
||||||
|
php-apcu php-xdebug php7.4-cli php7.4-curl php7.4-fpm php7.4-mbstring php7.4-mysql php7.4-zip php7.4-xml
|
||||||
|
|
||||||
|
# Configure Nginx with PHP
|
||||||
|
RUN mkdir -p /run/php
|
||||||
|
COPY configs/nginx.conf /etc/nginx/sites-available/default
|
||||||
|
RUN echo "xdebug.remote_enable = 1" >> /etc/php/7.4/mods-available/xdebug.ini
|
||||||
|
RUN echo "xdebug.remote_autostart = 1" >> /etc/php/7.4/mods-available/xdebug.ini
|
||||||
|
RUN echo "xdebug.remote_connect_back = 1" >> /etc/php/7.4/mods-available/xdebug.ini
|
||||||
|
|
||||||
|
# Install Composer
|
||||||
|
COPY scripts/install-composer.sh install-composer.sh
|
||||||
|
RUN ./install-composer.sh
|
||||||
|
|
||||||
|
# Install Node.js and required packages
|
||||||
|
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
|
||||||
|
RUN apt install -y nodejs
|
||||||
|
RUN npm install -g uglify-js clean-css-cli svgo yarn
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
VOLUME /var/www/rvr
|
||||||
|
WORKDIR /var/www/rvr
|
||||||
|
|
||||||
|
ENTRYPOINT /usr/sbin/php-fpm7.4 -F & /usr/sbin/nginx -g 'daemon off;'
|
6
docker/Dockerfile-test
Normal file
6
docker/Dockerfile-test
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
FROM ubuntu:focal
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
|
RUN apt update && apt install -y curl git unzip php7.4-cli php7.4-mbstring php7.4-xml
|
||||||
|
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
@ -1,9 +1,3 @@
|
|||||||
map $http_x_forwarded_proto $forwarded_scheme {
|
|
||||||
default $scheme;
|
|
||||||
http http;
|
|
||||||
https https;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80 default_server;
|
listen 80 default_server;
|
||||||
listen [::]:80 default_server;
|
listen [::]:80 default_server;
|
||||||
@ -20,8 +14,7 @@ server {
|
|||||||
|
|
||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
include snippets/fastcgi-php.conf;
|
include snippets/fastcgi-php.conf;
|
||||||
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
|
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||||
fastcgi_param REQUEST_SCHEME $forwarded_scheme;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ /\.ht {
|
location ~ /\.ht {
|
||||||
|
@ -1 +0,0 @@
|
|||||||
0 * * * * /var/www/rvr/rvr db:maintain
|
|
@ -1,36 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Installing Composer packages..."
|
|
||||||
if [ -f .env ]; then
|
|
||||||
composer install
|
|
||||||
else
|
|
||||||
composer create-project
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Installing Yarn packages..."
|
|
||||||
(cd public/static && yarn install)
|
|
||||||
|
|
||||||
echo "Migrating DB..."
|
|
||||||
./rvr db:migrate
|
|
||||||
|
|
||||||
echo "Set runner user based on owner of .env..."
|
|
||||||
if ! getent group rvr; then
|
|
||||||
USER_GID=$(stat -c "%g" .env)
|
|
||||||
groupadd --gid $USER_GID rvr
|
|
||||||
fi
|
|
||||||
if ! id -u rvr; then
|
|
||||||
USER_UID=$(stat -c "%u" .env)
|
|
||||||
useradd --uid $USER_UID --gid $USER_GID rvr
|
|
||||||
fi
|
|
||||||
sed -i -e "s/^user = .*$/user = rvr/g" -e "s/^group = .*$/group = rvr/g" /etc/php/8.1/fpm/pool.d/www.conf
|
|
||||||
|
|
||||||
set +e
|
|
||||||
|
|
||||||
/usr/sbin/php-fpm8.1 -F &
|
|
||||||
/usr/sbin/nginx -g 'daemon off;' &
|
|
||||||
|
|
||||||
wait -n
|
|
||||||
|
|
||||||
exit $?
|
|
@ -1,31 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Migrating DB..."
|
|
||||||
./rvr db:migrate
|
|
||||||
|
|
||||||
echo "Installing crontab..."
|
|
||||||
/usr/bin/crontab docker/scripts/cron
|
|
||||||
|
|
||||||
echo "Set runner user based on owner of .env..."
|
|
||||||
if ! getent group rvr; then
|
|
||||||
USER_GID=$(stat -c "%g" .env)
|
|
||||||
groupadd --gid $USER_GID rvr
|
|
||||||
fi
|
|
||||||
if ! id -u rvr; then
|
|
||||||
USER_UID=$(stat -c "%u" .env)
|
|
||||||
useradd --uid $USER_UID --gid $USER_GID rvr
|
|
||||||
fi
|
|
||||||
chown -R rvr:rvr cache
|
|
||||||
sed -i -e "s/^user = .*$/user = rvr/g" -e "s/^group = .*$/group = rvr/g" /etc/php/8.1/fpm/pool.d/www.conf
|
|
||||||
|
|
||||||
set +e
|
|
||||||
|
|
||||||
/usr/sbin/cron -f &
|
|
||||||
/usr/sbin/php-fpm8.1 -F &
|
|
||||||
/usr/sbin/nginx -g 'daemon off;' &
|
|
||||||
|
|
||||||
wait -n
|
|
||||||
|
|
||||||
exit $?
|
|
@ -1,14 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
apt update
|
|
||||||
apt install -y ca-certificates curl gnupg
|
|
||||||
mkdir -p /etc/apt/keyrings
|
|
||||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
|
||||||
|
|
||||||
NODE_MAJOR=18
|
|
||||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
|
||||||
|
|
||||||
apt update
|
|
||||||
apt install -y nodejs
|
|
@ -1,27 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Installing Composer packages..."
|
|
||||||
composer create-project --no-dev
|
|
||||||
|
|
||||||
echo "Installing Yarn packages..."
|
|
||||||
(cd public/static && yarn install)
|
|
||||||
|
|
||||||
echo "Updating version info..."
|
|
||||||
VERSION=$(git describe --tags --always --match "Release_*" HEAD)
|
|
||||||
REVISION=$(git rev-parse --short HEAD)
|
|
||||||
REVISION_DATE=$(git show -s --format=%aI HEAD)
|
|
||||||
sed -i -E "s/const VERSION = '(.*)';/const VERSION = '${VERSION}';/" app.php
|
|
||||||
sed -i -E "s/const REVISION = '(.*)';/const REVISION = '${REVISION}';/" app.php
|
|
||||||
sed -i -E "s/const REVISION_DATE = '(.*)';/const REVISION_DATE = '${REVISION_DATE}';/" app.php
|
|
||||||
|
|
||||||
echo "Minifying JS, CSS and SVG files..."
|
|
||||||
find public/static/js -type f -iname '*.js' -exec uglifyjs {} -c -m -o {} \;
|
|
||||||
find public/static/css -type f -iname '*.css' -exec cleancss {} -o {} \;
|
|
||||||
find public/static/img -type f -iname '*.svg' -exec svgo {} -o {} \;
|
|
||||||
|
|
||||||
echo "Linking view files..."
|
|
||||||
./rvr view:link
|
|
||||||
|
|
||||||
rm .env
|
|
9
mail/signup-noconfirm.html
Normal file
9
mail/signup-noconfirm.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
Hi,
|
||||||
|
<br><br>
|
||||||
|
You recently signed up on {{APP_NAME}} with this Google account ({{EMAIL}}).
|
||||||
|
<br><br>
|
||||||
|
Have fun on {{APP_NAME}}!
|
||||||
|
<br><br>
|
||||||
|
Regards,<br>
|
||||||
|
{{APP_NAME}}<br>
|
||||||
|
<a href="{{BASE_URL}}" title="{{APP_NAME}}">{{BASE_URL}}</a>
|
18
mail/signup.html
Normal file
18
mail/signup.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
Hi,
|
||||||
|
<br><br>
|
||||||
|
You recently signed up on {{APP_NAME}} with this email address ({{EMAIL}}).
|
||||||
|
To activate your account, please click on the following link:<br>
|
||||||
|
<a href="{{ACTIVATE_LINK}}" title="Account activation">{{ACTIVATE_LINK}}</a>
|
||||||
|
<br><br>
|
||||||
|
You can activate your account until {{ACTIVATABLE_UNTIL}}.
|
||||||
|
If you don't activate your account, your email address will be permanently deleted after this point of time.
|
||||||
|
<br><br>
|
||||||
|
If you did not sign up on {{APP_NAME}} or changed your mind, no further action is required.
|
||||||
|
However if you want to immediately delete your email address, please click on the following link:<br>
|
||||||
|
<a href="{{CANCEL_LINK}}" title="Sign up cancellation">{{CANCEL_LINK}}</a>
|
||||||
|
<br><br>
|
||||||
|
Have fun on {{APP_NAME}}!
|
||||||
|
<br><br>
|
||||||
|
Regards,<br>
|
||||||
|
{{APP_NAME}}<br>
|
||||||
|
<a href="{{BASE_URL}}" title="{{APP_NAME}}">{{BASE_URL}}</a>
|
@ -108,10 +108,6 @@ p.small, span.small, td.small {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.big, span.big, td.big {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.red {
|
.red {
|
||||||
color: #a80908;
|
color: #a80908;
|
||||||
}
|
}
|
||||||
@ -259,11 +255,11 @@ button.red:enabled:hover, button.red:enabled:focus, a.button.red:hover, a.button
|
|||||||
}
|
}
|
||||||
|
|
||||||
button.yellow, a.button.yellow {
|
button.yellow, a.button.yellow {
|
||||||
background-color: #daa520;
|
background-color: #e8a349;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.yellow:enabled:hover, button.yellow:enabled:focus, a.button.yellow:hover, a.button.yellow:focus {
|
button.yellow:enabled:hover, button.yellow:enabled:focus, a.button.yellow:hover, a.button.yellow:focus {
|
||||||
background-color: #b8860b;
|
background-color: #c37713;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.green, a.button.green {
|
button.green, a.button.green {
|
||||||
@ -401,20 +397,16 @@ header>p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
header>p>span {
|
header>p>span {
|
||||||
padding-left: 8px;
|
padding-left: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
header>p>span>a:link, header>p>span>a:visited {
|
header>p>span>a:link, header>p>span>a:visited {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
header>p>span>a:hover, header>p>span>a:focus {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
header>p>span:not(:last-child) {
|
header>p>span:not(:last-child) {
|
||||||
border-right: solid white 1px;
|
border-right: solid white 1px;
|
||||||
padding-right: 8px;
|
padding-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
@ -541,15 +533,6 @@ p.formLabel {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.label {
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: #555555;
|
|
||||||
color: #ffffff;
|
|
||||||
padding: 2px 4px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 424px) {
|
@media screen and (max-width: 424px) {
|
||||||
div.gridContainer {
|
div.gridContainer {
|
||||||
grid-template-columns: auto;
|
grid-template-columns: auto;
|
||||||
|
@ -59,10 +59,11 @@ var Account = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
var authenticateWithGoogleButton = document.getElementById('authenticateWithGoogleButton');
|
document.getElementById('authenticateWithGoogleButton').onclick = function () {
|
||||||
if (authenticateWithGoogleButton) {
|
|
||||||
authenticateWithGoogleButton.onclick = function () {
|
|
||||||
Account.openGoogleAuthenticate();
|
Account.openGoogleAuthenticate();
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
document.getElementsByTagName('form')[0].onreset = function () {
|
||||||
|
Account.resetGoogleAuthentication();
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
(function () {
|
|
||||||
const element = document.getElementById('transactionForm').elements['event_id'];
|
|
||||||
const select = new TomSelect(element, {
|
|
||||||
valueField: 'value',
|
|
||||||
labelField: 'label',
|
|
||||||
searchField: 'label',
|
|
||||||
loadThrottle: 300,
|
|
||||||
load: function (query, callback) {
|
|
||||||
var self = this;
|
|
||||||
RVR.httpRequest('GET', searchEventUrl.replace('QUERY', encodeURIComponent(query)), function () {
|
|
||||||
self.clearOptions();
|
|
||||||
callback(this.response.results);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
select.on('change', function (value) {
|
|
||||||
this.clearOptions();
|
|
||||||
});
|
|
||||||
|
|
||||||
select.on('blur', function (value) {
|
|
||||||
this.clearOptions();
|
|
||||||
});
|
|
||||||
|
|
||||||
select.on('type', function (value) {
|
|
||||||
if (value === '') {
|
|
||||||
this.clearOptions();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
@ -64,9 +64,6 @@ var RVR = {
|
|||||||
|
|
||||||
formError.style.display = 'block';
|
formError.style.display = 'block';
|
||||||
formError.innerHTML = this.response.error.errorText;
|
formError.innerHTML = this.response.error.errorText;
|
||||||
if (typeof grecaptcha !== 'undefined') {
|
|
||||||
grecaptcha.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -100,7 +97,7 @@ var RVR = {
|
|||||||
RVR.showModalWithContent('Confirmation', this.dataset.confirmation, [
|
RVR.showModalWithContent('Confirmation', this.dataset.confirmation, [
|
||||||
{
|
{
|
||||||
type: 'button',
|
type: 'button',
|
||||||
html: this.dataset.confirmationButton ? this.dataset.confirmationButton : this.innerHTML,
|
text: self.textContent,
|
||||||
classNames: ['red'],
|
classNames: ['red'],
|
||||||
onclick: function() {
|
onclick: function() {
|
||||||
var event = new Event('submit', {'bubbles': true, 'cancelable': true});
|
var event = new Event('submit', {'bubbles': true, 'cancelable': true});
|
||||||
@ -150,11 +147,7 @@ var RVR = {
|
|||||||
|
|
||||||
button.classList.add('marginTop');
|
button.classList.add('marginTop');
|
||||||
button.classList.add('marginRight');
|
button.classList.add('marginRight');
|
||||||
if (typeof extraButton.html !== 'undefined') {
|
|
||||||
button.innerHTML = extraButton.html;
|
|
||||||
} else {
|
|
||||||
button.textContent = extraButton.text;
|
button.textContent = extraButton.text;
|
||||||
}
|
|
||||||
|
|
||||||
if (extraButton.type === 'a') {
|
if (extraButton.type === 'a') {
|
||||||
button.href = extraButton.href;
|
button.href = extraButton.href;
|
||||||
|
5
public/static/package-lock.json
generated
5
public/static/package-lock.json
generated
@ -2,11 +2,6 @@
|
|||||||
"requires": true,
|
"requires": true,
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": {
|
|
||||||
"version": "6.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.0.tgz",
|
|
||||||
"integrity": "sha512-0NyytTlPJwB/BF5LtRV8rrABDbe3TdTXqNB3PdZ+UUUZAEIrdOJdmABqKjt4AXwIoJNaRVVZEXxpNrqvE1GAYQ=="
|
|
||||||
},
|
|
||||||
"@orchidjs/sifter": {
|
"@orchidjs/sifter": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@orchidjs/sifter/-/sifter-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@orchidjs/sifter/-/sifter-1.0.3.tgz",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^6.4.0",
|
|
||||||
"leaflet": "^1.6.0",
|
"leaflet": "^1.6.0",
|
||||||
"leaflet.markercluster": "^1.4.1",
|
"leaflet.markercluster": "^1.4.1",
|
||||||
"tom-select": "^2.2.2"
|
"tom-select": "^2.2.2"
|
||||||
|
@ -2,23 +2,6 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@fortawesome/fontawesome-free@^6.4.0":
|
|
||||||
version "6.7.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.1.tgz#160a48730d533ec77578ed0141661a8f0150a71d"
|
|
||||||
integrity sha512-ALIk/MOh5gYe1TG/ieS5mVUsk7VUIJTJKPMK9rFFqOgfp0Q3d5QiBXbcOMwUvs37fyZVCz46YjOE6IFeOAXCHA==
|
|
||||||
|
|
||||||
"@orchidjs/sifter@^1.1.0":
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@orchidjs/sifter/-/sifter-1.1.0.tgz#b36154ad0cda4898305d1ac44f318b41048a0438"
|
|
||||||
integrity sha512-mYwHCfr736cIWWdhhSZvDbf90AKt2xyrJspKFC3qyIJG1LtrJeJunYEqCGG4Aq2ijENbc4WkOjszcvNaIAS/pQ==
|
|
||||||
dependencies:
|
|
||||||
"@orchidjs/unicode-variants" "^1.1.2"
|
|
||||||
|
|
||||||
"@orchidjs/unicode-variants@^1.1.2":
|
|
||||||
version "1.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@orchidjs/unicode-variants/-/unicode-variants-1.1.2.tgz#1fd71791a67fdd1591ebe0dcaadd3964537a824e"
|
|
||||||
integrity sha512-5DobW1CHgnBROOEpFlEXytED5OosEWESFvg/VYmH0143oXcijYTprRYJTs+55HzGM4IqxiLFSuqEzu9mPNwVsA==
|
|
||||||
|
|
||||||
leaflet.markercluster@^1.4.1:
|
leaflet.markercluster@^1.4.1:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/leaflet.markercluster/-/leaflet.markercluster-1.4.1.tgz#b53f2c4f2ca7306ddab1dbb6f1861d5e8aa6c5e5"
|
resolved "https://registry.yarnpkg.com/leaflet.markercluster/-/leaflet.markercluster-1.4.1.tgz#b53f2c4f2ca7306ddab1dbb6f1861d5e8aa6c5e5"
|
||||||
@ -28,11 +11,3 @@ leaflet@^1.6.0:
|
|||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.6.0.tgz#aecbb044b949ec29469eeb31c77a88e2f448f308"
|
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.6.0.tgz#aecbb044b949ec29469eeb31c77a88e2f448f308"
|
||||||
integrity sha512-CPkhyqWUKZKFJ6K8umN5/D2wrJ2+/8UIpXppY7QDnUZW5bZL5+SEI2J7GBpwh4LIupOKqbNSQXgqmrEJopHVNQ==
|
integrity sha512-CPkhyqWUKZKFJ6K8umN5/D2wrJ2+/8UIpXppY7QDnUZW5bZL5+SEI2J7GBpwh4LIupOKqbNSQXgqmrEJopHVNQ==
|
||||||
|
|
||||||
tom-select@^2.2.2:
|
|
||||||
version "2.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/tom-select/-/tom-select-2.4.1.tgz#6a0b6df8af3df7b09b22dd965eb75ce4d1c547bc"
|
|
||||||
integrity sha512-adI8H8+wk8RRzHYLQ3bXSk2Q+FAq/kzAATrcWlJ2fbIrEzb0VkwaXzKHTAlBwSJrhqbPJvhV/0eypFkED/nAug==
|
|
||||||
dependencies:
|
|
||||||
"@orchidjs/sifter" "^1.1.0"
|
|
||||||
"@orchidjs/unicode-variants" "^1.1.2"
|
|
||||||
|
162
scripts/deploy-to-multiple-worktrees.py
Executable file
162
scripts/deploy-to-multiple-worktrees.py
Executable file
@ -0,0 +1,162 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
# Usage: ./deploy-to-multiple-worktrees.py REPO_PATH WORKTREE_DEVELOPMENT_PATH WORKTREE_PRODUCTION_PATH
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
|
||||||
|
WORKTREE_REGEX = r"^worktree (.*)\nHEAD ([a-f0-9]*)\n(?:branch refs\/heads\/(.*)|detached)$"
|
||||||
|
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
print("Usage: ./deploy-to-multiple-worktrees.py REPO_PATH WORKTREE_DEVELOPMENT_PATH WORKTREE_PRODUCTION_PATH")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
REPO = os.path.abspath(sys.argv[1])
|
||||||
|
WORKTREE_DEVELOPMENT = os.path.abspath(sys.argv[2])
|
||||||
|
WORKTREE_PRODUCTION = os.path.abspath(sys.argv[3])
|
||||||
|
|
||||||
|
class Worktree:
|
||||||
|
def __init__(self, path, branch, revision, version):
|
||||||
|
self.path = path
|
||||||
|
self.branch = branch
|
||||||
|
self.revision = revision
|
||||||
|
self.version = version
|
||||||
|
self.newRevision = None
|
||||||
|
self.newVersion = None
|
||||||
|
|
||||||
|
def getDataForWorktrees():
|
||||||
|
ret = subprocess.check_output(["git", "worktree", "list", "--porcelain"], cwd=REPO).decode().strip()
|
||||||
|
blocks = ret.split("\n\n")
|
||||||
|
|
||||||
|
worktrees = []
|
||||||
|
|
||||||
|
for block in blocks:
|
||||||
|
matches = re.search(WORKTREE_REGEX, block)
|
||||||
|
|
||||||
|
if matches:
|
||||||
|
path = matches.group(1)
|
||||||
|
revision = matches.group(2)
|
||||||
|
branch = matches.group(3)
|
||||||
|
version = getVersion(revision)
|
||||||
|
|
||||||
|
worktrees.append(Worktree(path, branch, revision, version))
|
||||||
|
|
||||||
|
return worktrees
|
||||||
|
|
||||||
|
def findWorktree(path):
|
||||||
|
for worktree in worktrees:
|
||||||
|
if worktree.path == path:
|
||||||
|
return worktree
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getVersion(branch):
|
||||||
|
return subprocess.check_output(["git", "describe", "--tags", "--always", "--match", "Release_*", branch], cwd=REPO).decode().strip()
|
||||||
|
|
||||||
|
def getRevisionForRef(ref):
|
||||||
|
return subprocess.check_output(["git", "rev-list", "-1", ref], cwd=REPO).decode().strip()
|
||||||
|
|
||||||
|
def getLatestReleaseTag():
|
||||||
|
process = subprocess.Popen(["git", "for-each-ref", "refs/tags/Release*", "--sort=-creatordate", "--format=%(refname:short)"], stdout=subprocess.PIPE, cwd=REPO)
|
||||||
|
|
||||||
|
for line in process.stdout:
|
||||||
|
tag = line.decode().rstrip()
|
||||||
|
|
||||||
|
if isTagVerified(tag):
|
||||||
|
return tag
|
||||||
|
|
||||||
|
print(f"[WARNING] Tag '{tag}' is not verified, skipping.")
|
||||||
|
|
||||||
|
raise Exception("No verified 'Release*' tag found!")
|
||||||
|
|
||||||
|
def isTagVerified(tag):
|
||||||
|
process = subprocess.run(["git", "tag", "--verify", tag], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=REPO)
|
||||||
|
|
||||||
|
return process.returncode == 0
|
||||||
|
|
||||||
|
def updateRepoFromRemote():
|
||||||
|
subprocess.call(["git", "fetch", "origin", "--prune", "--prune-tags"], cwd=REPO)
|
||||||
|
|
||||||
|
def checkoutWorktree(worktreePath, ref):
|
||||||
|
subprocess.call(["git", "checkout", "-f", ref], cwd=worktreePath)
|
||||||
|
|
||||||
|
def cleanWorktree(worktreePath):
|
||||||
|
subprocess.call(["git", "clean", "-f", "-d"], cwd=worktreePath)
|
||||||
|
|
||||||
|
def updateAppInWorktree(worktreePath):
|
||||||
|
subprocess.call([worktreePath + "/scripts/update.sh"], cwd=worktreePath)
|
||||||
|
|
||||||
|
def updateAppVersionInWorktree(worktreePath):
|
||||||
|
subprocess.call([worktreePath + "/scripts/update-version.sh"], cwd=worktreePath)
|
||||||
|
|
||||||
|
worktrees = getDataForWorktrees()
|
||||||
|
|
||||||
|
updateRepoFromRemote()
|
||||||
|
|
||||||
|
print("Repo is updated from origin")
|
||||||
|
|
||||||
|
print("----------------------------------------------")
|
||||||
|
print("----------------------------------------------")
|
||||||
|
|
||||||
|
developmentWorktree = findWorktree(WORKTREE_DEVELOPMENT)
|
||||||
|
|
||||||
|
developmentWorktree.newRevision = getRevisionForRef(developmentWorktree.branch)
|
||||||
|
developmentWorktree.newVersion = getVersion(developmentWorktree.revision)
|
||||||
|
|
||||||
|
print("DEVELOPMENT (" + developmentWorktree.path + ") is on branch " + developmentWorktree.branch)
|
||||||
|
print(developmentWorktree.revision + " = " + developmentWorktree.branch + " (" + developmentWorktree.version + ")")
|
||||||
|
print(developmentWorktree.newRevision + " = origin/" + developmentWorktree.branch + " (" + developmentWorktree.newVersion + ")")
|
||||||
|
|
||||||
|
if developmentWorktree.revision != developmentWorktree.newRevision:
|
||||||
|
print("-> DEVELOPMENT (" + developmentWorktree.path + ") will be UPDATED")
|
||||||
|
print("----------------------------------------------")
|
||||||
|
|
||||||
|
checkoutWorktree(developmentWorktree.path, developmentWorktree.branch)
|
||||||
|
cleanWorktree(developmentWorktree.path)
|
||||||
|
|
||||||
|
print(developmentWorktree.path + " is checked out to " + developmentWorktree.branch + " and cleaned")
|
||||||
|
|
||||||
|
updateAppInWorktree(developmentWorktree.path)
|
||||||
|
updateAppVersionInWorktree(developmentWorktree.path)
|
||||||
|
|
||||||
|
print("RVR is updated in " + developmentWorktree.path)
|
||||||
|
elif developmentWorktree.version != developmentWorktree.newVersion:
|
||||||
|
print("-> DEVELOPMENT " + developmentWorktree.path + "'s version info will be UPDATED")
|
||||||
|
|
||||||
|
updateAppVersionInWorktree(developmentWorktree.path)
|
||||||
|
|
||||||
|
print("RVR version is updated in " + developmentWorktree.path)
|
||||||
|
else:
|
||||||
|
print("-> DEVELOPMENT (" + developmentWorktree.path + ") WON'T be updated")
|
||||||
|
|
||||||
|
print("----------------------------------------------")
|
||||||
|
print("----------------------------------------------")
|
||||||
|
|
||||||
|
productionWorktree = findWorktree(WORKTREE_PRODUCTION)
|
||||||
|
|
||||||
|
productionWorktree.newVersion = getLatestReleaseTag()
|
||||||
|
productionWorktree.newRevision = getRevisionForRef(productionWorktree.newVersion)
|
||||||
|
|
||||||
|
print("PRODUCTION (" + productionWorktree.path + ")")
|
||||||
|
print(productionWorktree.revision + " = " + productionWorktree.version)
|
||||||
|
print(productionWorktree.newRevision + " = " + productionWorktree.newVersion)
|
||||||
|
|
||||||
|
if productionWorktree.revision != productionWorktree.newRevision:
|
||||||
|
print("-> PRODUCTION (" + productionWorktree.path + ") will be UPDATED")
|
||||||
|
|
||||||
|
checkoutWorktree(productionWorktree.path, productionWorktree.newRevision)
|
||||||
|
cleanWorktree(productionWorktree.path)
|
||||||
|
|
||||||
|
print(productionWorktree.path + " is checked out to " + productionWorktree.newRevision + " and cleaned")
|
||||||
|
|
||||||
|
updateAppInWorktree(productionWorktree.path)
|
||||||
|
updateAppVersionInWorktree(productionWorktree.path)
|
||||||
|
|
||||||
|
print("RVR is updated in " + productionWorktree.path)
|
||||||
|
else:
|
||||||
|
print("-> PRODUCTION (" + productionWorktree.path + ") WON'T be updated")
|
||||||
|
|
||||||
|
print("----------------------------------------------")
|
||||||
|
print("----------------------------------------------")
|
32
scripts/install.sh
Executable file
32
scripts/install.sh
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
ROOT_DIR=$(dirname $(readlink -f "$0"))/..
|
||||||
|
|
||||||
|
. ${ROOT_DIR}/.env
|
||||||
|
|
||||||
|
if [ -f ${ROOT_DIR}/installed ]; then
|
||||||
|
echo "RVR is already installed! To force reinstall, delete file 'installed' from the root directory!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing Yarn packages..."
|
||||||
|
(cd ${ROOT_DIR}/public/static && yarn install)
|
||||||
|
|
||||||
|
echo "Installing RVR DB..."
|
||||||
|
mysql --host=${DB_HOST} --user=${DB_USER} --password=${DB_PASSWORD} ${DB_NAME} < ${ROOT_DIR}/database/rvr.sql
|
||||||
|
|
||||||
|
echo "Migrating DB..."
|
||||||
|
(cd ${ROOT_DIR} && ./rvr db:migrate)
|
||||||
|
|
||||||
|
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
||||||
|
echo "Minifying JS, CSS and SVG files..."
|
||||||
|
${ROOT_DIR}/scripts/minify.sh
|
||||||
|
|
||||||
|
echo "Linking view files..."
|
||||||
|
(cd ${ROOT_DIR} && ./rvr view:link)
|
||||||
|
else
|
||||||
|
echo "Creating the first user..."
|
||||||
|
(cd ${ROOT_DIR} && ./rvr user:add rvr@rvr.dev 123456 admin)
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch ${ROOT_DIR}/installed
|
11
scripts/minify.sh
Executable file
11
scripts/minify.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
ROOT_DIR=$(dirname $(readlink -f "$0"))/..
|
||||||
|
|
||||||
|
. ${ROOT_DIR}/.env
|
||||||
|
|
||||||
|
find ${ROOT_DIR}/public/static/js -type f -iname '*.js' -exec uglifyjs {} -c -m -o {} \;
|
||||||
|
|
||||||
|
find ${ROOT_DIR}/public/static/css -type f -iname '*.css' -exec cleancss {} -o {} \;
|
||||||
|
|
||||||
|
find ${ROOT_DIR}/public/static/img -type f -iname '*.svg' -exec svgo {} -o {} \;
|
17
scripts/update-version.sh
Executable file
17
scripts/update-version.sh
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
ROOT_DIR=$(dirname $(readlink -f "$0"))/..
|
||||||
|
|
||||||
|
. ${ROOT_DIR}/.env
|
||||||
|
|
||||||
|
cd ${ROOT_DIR}
|
||||||
|
|
||||||
|
echo "Updating version info..."
|
||||||
|
|
||||||
|
VERSION=$(git describe --tags --always --match "Release_*" HEAD)
|
||||||
|
REVISION=$(git rev-parse --short HEAD)
|
||||||
|
REVISION_DATE=$(git show -s --format=%aI HEAD)
|
||||||
|
|
||||||
|
sed -i -E "s/const VERSION = '(.*)';/const VERSION = '${VERSION}';/" app.php
|
||||||
|
sed -i -E "s/const REVISION = '(.*)';/const REVISION = '${REVISION}';/" app.php
|
||||||
|
sed -i -E "s/const REVISION_DATE = '(.*)';/const REVISION_DATE = '${REVISION_DATE}';/" app.php
|
26
scripts/update.sh
Executable file
26
scripts/update.sh
Executable file
@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
ROOT_DIR=$(dirname $(readlink -f "$0"))/..
|
||||||
|
|
||||||
|
. ${ROOT_DIR}/.env
|
||||||
|
|
||||||
|
echo "Installing Composer packages..."
|
||||||
|
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
||||||
|
(cd ${ROOT_DIR} && composer install --no-dev)
|
||||||
|
else
|
||||||
|
(cd ${ROOT_DIR} && composer install --dev)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing Yarn packages..."
|
||||||
|
(cd ${ROOT_DIR}/public/static && yarn install)
|
||||||
|
|
||||||
|
echo "Migrating DB..."
|
||||||
|
(cd ${ROOT_DIR} && ./rvr db:migrate)
|
||||||
|
|
||||||
|
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
||||||
|
echo "Minifying JS, CSS and SVG files..."
|
||||||
|
${ROOT_DIR}/scripts/minify.sh
|
||||||
|
|
||||||
|
echo "Linking view files..."
|
||||||
|
(cd ${ROOT_DIR} && ./rvr view:link)
|
||||||
|
fi
|
@ -5,8 +5,6 @@ use SokoWeb\Database\Query\Modify;
|
|||||||
use SokoWeb\Database\Query\Select;
|
use SokoWeb\Database\Query\Select;
|
||||||
use SokoWeb\Interfaces\Database\IResultSet;
|
use SokoWeb\Interfaces\Database\IResultSet;
|
||||||
use RVR\Repository\UserPasswordResetterRepository;
|
use RVR\Repository\UserPasswordResetterRepository;
|
||||||
use RVR\Repository\OAuthTokenRepository;
|
|
||||||
use RVR\Repository\OAuthSessionRepository;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
@ -15,17 +13,11 @@ class MaintainDatabaseCommand extends Command
|
|||||||
{
|
{
|
||||||
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
||||||
|
|
||||||
private OAuthTokenRepository $oauthTokenRepository;
|
|
||||||
|
|
||||||
private OAuthSessionRepository $oauthSessionRepository;
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
$this->oauthTokenRepository = new OAuthTokenRepository();
|
|
||||||
$this->oauthSessionRepository = new OAuthSessionRepository();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configure(): void
|
public function configure(): void
|
||||||
@ -39,8 +31,6 @@ class MaintainDatabaseCommand extends Command
|
|||||||
try {
|
try {
|
||||||
$this->deleteExpiredPasswordResetters();
|
$this->deleteExpiredPasswordResetters();
|
||||||
$this->deleteExpiredSessions();
|
$this->deleteExpiredSessions();
|
||||||
$this->deleteExpiredOauthTokens();
|
|
||||||
$this->deleteExpiredOauthSessions();
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$output->writeln('<error>Maintenance failed!</error>');
|
$output->writeln('<error>Maintenance failed!</error>');
|
||||||
$output->writeln('');
|
$output->writeln('');
|
||||||
@ -69,7 +59,7 @@ class MaintainDatabaseCommand extends Command
|
|||||||
//TODO: model may be used for sessions too
|
//TODO: model may be used for sessions too
|
||||||
$select = new Select(\Container::$dbConnection, 'sessions');
|
$select = new Select(\Container::$dbConnection, 'sessions');
|
||||||
$select->columns(['id']);
|
$select->columns(['id']);
|
||||||
$select->where('updated', '<', (new DateTime('-1 days'))->format('Y-m-d H:i:s'));
|
$select->where('updated', '<', (new DateTime('-7 days'))->format('Y-m-d H:i:s'));
|
||||||
|
|
||||||
$result = $select->execute();
|
$result = $select->execute();
|
||||||
|
|
||||||
@ -79,21 +69,4 @@ class MaintainDatabaseCommand extends Command
|
|||||||
$modify->delete();
|
$modify->delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deleteExpiredOauthTokens(): void
|
|
||||||
{
|
|
||||||
foreach ($this->oauthTokenRepository->getAllExpired() as $oauthToken) {
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($oauthToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function deleteExpiredOauthSessions(): void
|
|
||||||
{
|
|
||||||
foreach ($this->oauthSessionRepository->getAllExpired() as $oauthSession) {
|
|
||||||
if ($this->oauthTokenRepository->countAllBySession($oauthSession) > 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($oauthSession);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,6 @@ class MigrateDatabaseCommand extends Command
|
|||||||
{
|
{
|
||||||
$db = \Container::$dbConnection;
|
$db = \Container::$dbConnection;
|
||||||
|
|
||||||
$this->createBaseDb();
|
|
||||||
|
|
||||||
$db->startTransaction();
|
$db->startTransaction();
|
||||||
|
|
||||||
$success = [];
|
$success = [];
|
||||||
@ -64,8 +62,10 @@ class MigrateDatabaseCommand extends Command
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createBaseDb()
|
private function readDir(string $type): array
|
||||||
{
|
{
|
||||||
|
$done = [];
|
||||||
|
|
||||||
$migrationTableExists = \Container::$dbConnection->query('SELECT count(*)
|
$migrationTableExists = \Container::$dbConnection->query('SELECT count(*)
|
||||||
FROM information_schema.tables
|
FROM information_schema.tables
|
||||||
WHERE table_schema = \'' . $_ENV['DB_NAME'] . '\'
|
WHERE table_schema = \'' . $_ENV['DB_NAME'] . '\'
|
||||||
@ -73,16 +73,6 @@ class MigrateDatabaseCommand extends Command
|
|||||||
->fetch(IResultSet::FETCH_NUM)[0];
|
->fetch(IResultSet::FETCH_NUM)[0];
|
||||||
|
|
||||||
if ($migrationTableExists != 0) {
|
if ($migrationTableExists != 0) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$dbConnection->multiQuery(file_get_contents(ROOT . '/database/rvr.sql'));
|
|
||||||
}
|
|
||||||
|
|
||||||
private function readDir(string $type): array
|
|
||||||
{
|
|
||||||
$done = [];
|
|
||||||
|
|
||||||
$select = new Select(\Container::$dbConnection, 'migrations');
|
$select = new Select(\Container::$dbConnection, 'migrations');
|
||||||
$select->columns(['migration']);
|
$select->columns(['migration']);
|
||||||
$select->where('type', '=', $type);
|
$select->where('type', '=', $type);
|
||||||
@ -93,6 +83,7 @@ class MigrateDatabaseCommand extends Command
|
|||||||
while ($migration = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
while ($migration = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
$done[] = $migration['migration'];
|
$done[] = $migration['migration'];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$path = ROOT . '/database/migrations/' . $type;
|
$path = ROOT . '/database/migrations/' . $type;
|
||||||
$dir = opendir($path);
|
$dir = opendir($path);
|
||||||
|
@ -11,7 +11,6 @@ use RVR\Repository\CommunityRepository;
|
|||||||
use RVR\Repository\CommunityMemberRepository;
|
use RVR\Repository\CommunityMemberRepository;
|
||||||
use RVR\Repository\CurrencyExchangeRateRepository;
|
use RVR\Repository\CurrencyExchangeRateRepository;
|
||||||
use RVR\Repository\CurrencyRepository;
|
use RVR\Repository\CurrencyRepository;
|
||||||
use RVR\Repository\EventRepository;
|
|
||||||
use RVR\Repository\TransactionRepository;
|
use RVR\Repository\TransactionRepository;
|
||||||
use RVR\Repository\UserRepository;
|
use RVR\Repository\UserRepository;
|
||||||
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
||||||
@ -33,8 +32,6 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
|
|
||||||
private TransactionRepository $transactionRepository;
|
private TransactionRepository $transactionRepository;
|
||||||
|
|
||||||
private EventRepository $eventRepository;
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->userRepository = new UserRepository();
|
$this->userRepository = new UserRepository();
|
||||||
@ -43,7 +40,6 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
$this->currencyRepository = new CurrencyRepository();
|
$this->currencyRepository = new CurrencyRepository();
|
||||||
$this->currencyExchangeRatesRepository = new CurrencyExchangeRateRepository();
|
$this->currencyExchangeRatesRepository = new CurrencyExchangeRateRepository();
|
||||||
$this->transactionRepository = new TransactionRepository();
|
$this->transactionRepository = new TransactionRepository();
|
||||||
$this->eventRepository = new EventRepository();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAuthenticationRequired(): bool
|
public function isAuthenticationRequired(): bool
|
||||||
@ -53,34 +49,45 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
|
|
||||||
public function getCommunityHome(): ?IContent
|
public function getCommunityHome(): ?IContent
|
||||||
{
|
{
|
||||||
if (!$this->checkPermission(\Container::$request->query('communitySlug'), false, $community, $ownCommunityMember)) {
|
if (!$this->checkPermission(\Container::$request->query('communityId'), false, $community, $ownCommunityMember)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$persistentDataManager->loadRelationsFromDb($community, false, ['main_currency']);
|
\Container::$persistentDataManager->loadRelationsFromDb($community, false, ['main_currency']);
|
||||||
|
|
||||||
/**
|
$balanceCalculator = new BalanceCalculator($community);
|
||||||
* @var User $user
|
$debts = $balanceCalculator->calculate();
|
||||||
*/
|
$debtItems = [];
|
||||||
$user = \Container::$request->user();
|
$debtBalance = 0.0;
|
||||||
$balanceCalculator = new BalanceCalculator($community, $user);
|
$outstandingItems = [];
|
||||||
$balance = $balanceCalculator->calculate();
|
$outstandingBalance = 0.0;
|
||||||
|
foreach ($debts as $debt) {
|
||||||
|
if ($debt['payer']->getId() === \Container::$request->user()->getUniqueId()) {
|
||||||
|
$debtBalance += $debt['amount'];
|
||||||
|
$debtItems[] = $debt;
|
||||||
|
}
|
||||||
|
if ($debt['payee']->getId() === \Container::$request->user()->getUniqueId()) {
|
||||||
|
$outstandingBalance += $debt['amount'];
|
||||||
|
$outstandingItems[] = $debt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$balance = $outstandingBalance - $debtBalance;
|
||||||
|
|
||||||
return new HtmlContent('communities/community', [
|
return new HtmlContent('communities/community', [
|
||||||
'community' => $community,
|
'community' => $community,
|
||||||
'upcomingAndRecentEvents' => iterator_to_array($this->eventRepository->getUpcomingAndRecentByCommunity($community, new DateTime(), 30, 3)),
|
'upcomingEvents' => [],
|
||||||
'debtItems' => $balance['debtItems'],
|
'debtItems' => $debtItems,
|
||||||
'debtBalance' => $balance['debtBalance'],
|
'debtBalance' => $debtBalance,
|
||||||
'outstandingItems' => $balance['outstandingItems'],
|
'outstandingItems' => $outstandingItems,
|
||||||
'outstandingBalance' => $balance['outstandingBalance'],
|
'outstandingBalance' => $outstandingBalance,
|
||||||
'balance' => $balance['absoluteBalance'],
|
'balance' => $balance,
|
||||||
'editPermission' => $ownCommunityMember->getOwner()
|
'editPermission' => $ownCommunityMember->getOwner()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCommunitySettings(): ?IContent
|
public function getCommunitySettings(): ?IContent
|
||||||
{
|
{
|
||||||
if (!$this->checkPermission(\Container::$request->query('communitySlug'), false, $community, $ownCommunityMember)) {
|
if (!$this->checkPermission(\Container::$request->query('communityId'), false, $community, $ownCommunityMember)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +106,7 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
|
|
||||||
public function getCommunityEdit(): ?IContent
|
public function getCommunityEdit(): ?IContent
|
||||||
{
|
{
|
||||||
if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
|
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,9 +124,9 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$communitySlug = \Container::$request->query('communitySlug');
|
$communityId = \Container::$request->query('communityId');
|
||||||
if ($communitySlug){
|
if ($communityId){
|
||||||
if (!$this->checkPermission($communitySlug, true, $community, $ownCommunityMember)) {
|
if (!$this->checkPermission($communityId, true, $community, $ownCommunityMember)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -138,7 +145,7 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
$community->setName($name);
|
$community->setName($name);
|
||||||
\Container::$persistentDataManager->saveToDb($community);
|
\Container::$persistentDataManager->saveToDb($community);
|
||||||
|
|
||||||
if (!$communitySlug) {
|
if (!$communityId) {
|
||||||
/**
|
/**
|
||||||
* @var User $user
|
* @var User $user
|
||||||
*/
|
*/
|
||||||
@ -161,50 +168,13 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'redirect' => ['target' => \Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()])]
|
'redirect' => ['target' => \Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()])]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteCommunity(): ?IContent
|
|
||||||
{
|
|
||||||
if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->transactionRepository->countAllByCommunity($community) > 0) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => ['errorText' => 'There are transactions for this community!']
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->eventRepository->countAllByCommunity($community) > 0) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => ['errorText' => 'There are events for this community!']
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->communityMemberRepository->getAllByCommunity($community) as $communityMember) {
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($communityMember);
|
|
||||||
}
|
|
||||||
|
|
||||||
$community->setMainCurrencyId(null);
|
|
||||||
\Container::$persistentDataManager->saveToDb($community);
|
|
||||||
|
|
||||||
foreach ($this->currencyRepository->getAllByCommunity($community) as $currency) {
|
|
||||||
foreach ($this->currencyExchangeRatesRepository->getAllByCurrency($currency) as $currencyExchangeRate) {
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($currencyExchangeRate);
|
|
||||||
}
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($currency);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($community);
|
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMembersEdit(): ?IContent
|
public function getMembersEdit(): ?IContent
|
||||||
{
|
{
|
||||||
if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
|
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,7 +186,7 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
|
|
||||||
public function saveMember(): ?IContent
|
public function saveMember(): ?IContent
|
||||||
{
|
{
|
||||||
if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
|
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +225,7 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
|
|
||||||
public function deleteMember(): ?IContent
|
public function deleteMember(): ?IContent
|
||||||
{
|
{
|
||||||
if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
|
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,7 +250,7 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
|
|
||||||
public function getCurrenciesEdit(): ?IContent
|
public function getCurrenciesEdit(): ?IContent
|
||||||
{
|
{
|
||||||
if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
|
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +262,7 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
|
|
||||||
public function saveCurrency(): ?IContent
|
public function saveCurrency(): ?IContent
|
||||||
{
|
{
|
||||||
if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
|
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,7 +298,7 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
|
|
||||||
public function deleteCurrency(): ?IContent
|
public function deleteCurrency(): ?IContent
|
||||||
{
|
{
|
||||||
if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
|
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,7 +324,7 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
|
|
||||||
public function getCurrencyExchangeRates(): ?IContent
|
public function getCurrencyExchangeRates(): ?IContent
|
||||||
{
|
{
|
||||||
if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
|
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,7 +345,7 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
|
|
||||||
public function saveCurrencyExchangeRate(): ?IContent
|
public function saveCurrencyExchangeRate(): ?IContent
|
||||||
{
|
{
|
||||||
if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
|
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,7 +378,7 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
|
|
||||||
public function deleteCurrencyExchangeRate(): ?IContent
|
public function deleteCurrencyExchangeRate(): ?IContent
|
||||||
{
|
{
|
||||||
if (!$this->checkPermission(\Container::$request->query('communitySlug'), true, $community, $ownCommunityMember)) {
|
if (!$this->checkPermission(\Container::$request->query('communityId'), true, $community, $ownCommunityMember)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,12 +415,12 @@ class CommunityController implements IAuthenticationRequired
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function checkPermission(
|
private function checkPermission(
|
||||||
string $communitySlug,
|
int $communityId,
|
||||||
bool $needToBeOwner,
|
bool $needToBeOwner,
|
||||||
?Community &$community,
|
?Community &$community,
|
||||||
?CommunityMember &$ownCommunityMember): bool
|
?CommunityMember &$ownCommunityMember): bool
|
||||||
{
|
{
|
||||||
$community = $this->communityRepository->getBySlug($communitySlug);
|
$community = $this->communityRepository->getById($communityId);
|
||||||
if ($community === null) {
|
if ($community === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,193 +0,0 @@
|
|||||||
<?php namespace RVR\Controller;
|
|
||||||
|
|
||||||
use Container;
|
|
||||||
use DateTime;
|
|
||||||
use RVR\Finance\BalanceCalculator;
|
|
||||||
use RVR\Finance\ExchangeRateCalculator;
|
|
||||||
use RVR\PersistentData\Model\Community;
|
|
||||||
use RVR\PersistentData\Model\CommunityMember;
|
|
||||||
use RVR\PersistentData\Model\Event;
|
|
||||||
use RVR\PersistentData\Model\User;
|
|
||||||
use RVR\Repository\CommunityMemberRepository;
|
|
||||||
use RVR\Repository\CommunityRepository;
|
|
||||||
use RVR\Repository\EventRepository;
|
|
||||||
use RVR\Repository\TransactionRepository;
|
|
||||||
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
|
||||||
use SokoWeb\Interfaces\Authorization\ISecured;
|
|
||||||
use SokoWeb\Interfaces\Response\IContent;
|
|
||||||
use SokoWeb\Response\HtmlContent;
|
|
||||||
use SokoWeb\Response\JsonContent;
|
|
||||||
|
|
||||||
class EventController implements IAuthenticationRequired, ISecured
|
|
||||||
{
|
|
||||||
private CommunityRepository $communityRepository;
|
|
||||||
|
|
||||||
private CommunityMemberRepository $communityMemberRepository;
|
|
||||||
|
|
||||||
private EventRepository $eventRepository;
|
|
||||||
|
|
||||||
private TransactionRepository $transactionRepository;
|
|
||||||
|
|
||||||
private ?Community $community;
|
|
||||||
|
|
||||||
private ?CommunityMember $ownCommunityMember;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->communityRepository = new CommunityRepository();
|
|
||||||
$this->communityMemberRepository = new CommunityMemberRepository();
|
|
||||||
$this->eventRepository = new EventRepository();
|
|
||||||
$this->transactionRepository = new TransactionRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isAuthenticationRequired(): bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function authorize(): bool
|
|
||||||
{
|
|
||||||
$communitySlug = \Container::$request->query('communitySlug');
|
|
||||||
$this->community = $this->communityRepository->getBySlug($communitySlug);
|
|
||||||
if ($this->community === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
$this->ownCommunityMember = $this->communityMemberRepository->getByCommunityAndUser($this->community, $user);
|
|
||||||
if ($this->ownCommunityMember === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEvents(): IContent
|
|
||||||
{
|
|
||||||
$itemsPerPage = 10;
|
|
||||||
$numberOfEvents = $this->eventRepository->countAllByCommunity($this->community);
|
|
||||||
$currentPage = Container::$request->query('page') ?: 1;
|
|
||||||
$events = $this->eventRepository->getPagedByCommunity(
|
|
||||||
$this->community,
|
|
||||||
$currentPage,
|
|
||||||
$itemsPerPage
|
|
||||||
);
|
|
||||||
|
|
||||||
return new HtmlContent('events/events', [
|
|
||||||
'community' => $this->community,
|
|
||||||
'pages' => ceil($numberOfEvents / $itemsPerPage),
|
|
||||||
'currentPage' => $currentPage,
|
|
||||||
'numberOfEvents' => $numberOfEvents,
|
|
||||||
'events' => $events
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function searchEvent(): IContent
|
|
||||||
{
|
|
||||||
$events = iterator_to_array($this->eventRepository->searchByTitle($this->community, Container::$request->query('q')));
|
|
||||||
$results = [];
|
|
||||||
foreach ($events as $event) {
|
|
||||||
$results[] = ['value' => $event->getId(), 'label' => $event->getTitle()];
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonContent([
|
|
||||||
'results' => $results
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEvent(): ?IContent
|
|
||||||
{
|
|
||||||
$event = $this->eventRepository->getBySlug(Container::$request->query('eventSlug'));
|
|
||||||
if (!$event) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Container::$persistentDataManager->loadRelationsFromDb($this->community, true, ['main_currency']);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
$balanceCalculator = new BalanceCalculator($this->community, $user, $event);
|
|
||||||
$balance = $balanceCalculator->calculate();
|
|
||||||
|
|
||||||
return new HtmlContent('events/event', [
|
|
||||||
'community' => $this->community,
|
|
||||||
'event' => $event,
|
|
||||||
'totalCost' => $this->sumTransactions($event),
|
|
||||||
'debtItems' => $balance['debtItems'],
|
|
||||||
'debtBalance' => $balance['debtBalance'],
|
|
||||||
'outstandingItems' => $balance['outstandingItems'],
|
|
||||||
'outstandingBalance' => $balance['outstandingBalance'],
|
|
||||||
'balance' => $balance['absoluteBalance'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEventEdit(): ?IContent
|
|
||||||
{
|
|
||||||
$eventSlug = Container::$request->query('eventSlug');
|
|
||||||
if ($eventSlug) {
|
|
||||||
$event = $this->eventRepository->getBySlug($eventSlug);
|
|
||||||
if ($event === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$event = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new HtmlContent('events/event_edit', [
|
|
||||||
'community' => $this->community,
|
|
||||||
'event' => $event
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function saveEvent(): ?IContent
|
|
||||||
{
|
|
||||||
$eventSlug = Container::$request->query('eventSlug');
|
|
||||||
if ($eventSlug) {
|
|
||||||
$event = $this->eventRepository->getBySlug($eventSlug);
|
|
||||||
} else {
|
|
||||||
$event = new Event();
|
|
||||||
$event->setCommunity($this->community);
|
|
||||||
}
|
|
||||||
|
|
||||||
$event->setTitle(Container::$request->post('title'));
|
|
||||||
$event->setDescription(Container::$request->post('description'));
|
|
||||||
$event->setStartDate(new DateTime(Container::$request->post('start')));
|
|
||||||
$event->setEndDate(new DateTime(Container::$request->post('end')));
|
|
||||||
Container::$persistentDataManager->saveToDb($event);
|
|
||||||
|
|
||||||
return new JsonContent([
|
|
||||||
'redirect' => ['target' => \Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $this->community->getSlug(), 'eventSlug' => $event->getSlug()])]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deleteEvent(): IContent
|
|
||||||
{
|
|
||||||
$event = $this->eventRepository->getBySlug(Container::$request->query('eventSlug'));
|
|
||||||
|
|
||||||
foreach ($this->transactionRepository->getAllByEvent($event) as $transaction) {
|
|
||||||
$transaction->setEventId(null);
|
|
||||||
Container::$persistentDataManager->saveToDb($transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
Container::$persistentDataManager->deleteFromDb($event);
|
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function sumTransactions(Event $event): float
|
|
||||||
{
|
|
||||||
$exchangeRateCalculator = new ExchangeRateCalculator($this->community->getMainCurrency());
|
|
||||||
$transactions = $this->transactionRepository->getAllByEvent($event, true, ['currency']);
|
|
||||||
$sum = 0.0;
|
|
||||||
|
|
||||||
foreach ($transactions as $transaction) {
|
|
||||||
$sum += $exchangeRateCalculator->calculate($transaction->getSum(), $transaction->getCurrency(), $transaction->getTimeDate());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sum;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
<?php namespace RVR\Controller;
|
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use Container;
|
|
||||||
use RVR\PersistentData\Model\Event;
|
|
||||||
use RVR\PersistentData\Model\User;
|
|
||||||
use RVR\Repository\EventRepository;
|
|
||||||
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
|
||||||
use SokoWeb\Interfaces\Response\IRedirect;
|
|
||||||
use SokoWeb\Response\HtmlContent;
|
|
||||||
use SokoWeb\Response\Redirect;
|
|
||||||
|
|
||||||
class EventRedirectController implements IAuthenticationRequired
|
|
||||||
{
|
|
||||||
private EventRepository $eventRepository;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->eventRepository = new EventRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isAuthenticationRequired(): bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEvent()
|
|
||||||
{
|
|
||||||
$currentEvent = $this->getCurrentEvent();
|
|
||||||
if ($currentEvent === null) {
|
|
||||||
return new HtmlContent('event_redirect/no_event');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Redirect(
|
|
||||||
\Container::$routeCollection->getRoute('community.event')
|
|
||||||
->generateLink([
|
|
||||||
'communitySlug' => $currentEvent->getCommunity()->getSlug(),
|
|
||||||
'eventSlug' => $currentEvent->getSlug()
|
|
||||||
]),
|
|
||||||
IRedirect::TEMPORARY
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEventNewTransaction()
|
|
||||||
{
|
|
||||||
$currentEvent = $this->getCurrentEvent();
|
|
||||||
if ($currentEvent === null) {
|
|
||||||
return new HtmlContent('event_redirect/no_event');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Redirect(
|
|
||||||
\Container::$routeCollection->getRoute('community.transactions.new')
|
|
||||||
->generateLink([
|
|
||||||
'communitySlug' => $currentEvent->getCommunity()->getSlug(),
|
|
||||||
'event' => $currentEvent->getSlug()
|
|
||||||
]),
|
|
||||||
IRedirect::TEMPORARY
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getCurrentEvent(): ?Event
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = Container::$request->user();
|
|
||||||
|
|
||||||
return $this->eventRepository->getCurrentByUser($user, new DateTime(), 30, true, ['community']);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +1,8 @@
|
|||||||
<?php namespace RVR\Controller;
|
<?php namespace RVR\Controller;
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use RVR\PersistentData\Model\Community;
|
use RVR\PersistentData\Model\Community;
|
||||||
use RVR\PersistentData\Model\User;
|
use RVR\PersistentData\Model\User;
|
||||||
use RVR\Repository\CommunityMemberRepository;
|
use RVR\Repository\CommunityMemberRepository;
|
||||||
use RVR\Repository\EventRepository;
|
|
||||||
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
||||||
use SokoWeb\Interfaces\Response\IContent;
|
use SokoWeb\Interfaces\Response\IContent;
|
||||||
use SokoWeb\Response\HtmlContent;
|
use SokoWeb\Response\HtmlContent;
|
||||||
@ -13,12 +11,9 @@ class HomeController implements IAuthenticationRequired
|
|||||||
{
|
{
|
||||||
private CommunityMemberRepository $communityMemberRepository;
|
private CommunityMemberRepository $communityMemberRepository;
|
||||||
|
|
||||||
private EventRepository $eventRepository;
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->communityMemberRepository = new CommunityMemberRepository();
|
$this->communityMemberRepository = new CommunityMemberRepository();
|
||||||
$this->eventRepository = new EventRepository();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAuthenticationRequired(): bool
|
public function isAuthenticationRequired(): bool
|
||||||
@ -44,7 +39,7 @@ class HomeController implements IAuthenticationRequired
|
|||||||
|
|
||||||
return new HtmlContent('home', [
|
return new HtmlContent('home', [
|
||||||
'communities' => $communities,
|
'communities' => $communities,
|
||||||
'upcomingAndRecentEvents' => iterator_to_array($this->eventRepository->getUpcomingAndRecentByUser($user, new DateTime(), 30, 3, true, ['community']))
|
'upcomingEvents' => []
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,7 +196,7 @@ class LoginController
|
|||||||
if ($user === null) {
|
if ($user === null) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'No user found with the given email address.'
|
'errorText' => 'No user found with the given email address / username.'
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -281,7 +281,7 @@ class LoginController
|
|||||||
$mail = new Mail();
|
$mail = new Mail();
|
||||||
$mail->addRecipient($email);
|
$mail->addRecipient($email);
|
||||||
$mail->setSubject($_ENV['APP_NAME'] . ' - Password reset');
|
$mail->setSubject($_ENV['APP_NAME'] . ' - Password reset');
|
||||||
$mail->setBodyFromTemplate('password-reset', [
|
$mail->setBodyFromTemplate('password.reset', [
|
||||||
'EMAIL' => $email,
|
'EMAIL' => $email,
|
||||||
'RESET_LINK' => \Container::$request->getBase() .
|
'RESET_LINK' => \Container::$request->getBase() .
|
||||||
\Container::$routeCollection->getRoute('password.reset')->generateLink(['token' => $token]),
|
\Container::$routeCollection->getRoute('password.reset')->generateLink(['token' => $token]),
|
||||||
|
79
src/Controller/OAuthAuthController.php
Normal file
79
src/Controller/OAuthAuthController.php
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?php namespace RVR\Controller;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use RVR\PersistentData\Model\OAuthToken;
|
||||||
|
use RVR\PersistentData\Model\User;
|
||||||
|
use RVR\Repository\OAuthClientRepository;
|
||||||
|
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
||||||
|
use SokoWeb\Interfaces\Response\IRedirect;
|
||||||
|
use SokoWeb\Response\Redirect;
|
||||||
|
use SokoWeb\Response\HtmlContent;
|
||||||
|
|
||||||
|
class OAuthAuthController implements IAuthenticationRequired
|
||||||
|
{
|
||||||
|
private OAuthClientRepository $oAuthClientRepository;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->oAuthClientRepository = new OAuthClientRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isAuthenticationRequired(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function auth()
|
||||||
|
{
|
||||||
|
$redirectUri = \Container::$request->query('redirect_uri');
|
||||||
|
$clientId = \Container::$request->query('client_id');
|
||||||
|
$scope = \Container::$request->query('scope') ? \Container::$request->query('scope'): '';
|
||||||
|
$state = \Container::$request->query('state');
|
||||||
|
$nonce = \Container::$request->query('nonce') ? \Container::$request->query('nonce'): '';
|
||||||
|
|
||||||
|
if (!$clientId || !$redirectUri || !$state) {
|
||||||
|
return new HtmlContent('oauth/oauth_error', ['error' => 'An invalid request was made. Please start authentication again.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$client = $this->oAuthClientRepository->getByClientId($clientId);
|
||||||
|
if ($client === null) {
|
||||||
|
return new HtmlContent('oauth/oauth_error', ['error' => 'Client is not authorized.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$redirectUriParsed = parse_url($redirectUri);
|
||||||
|
$redirectUriBase = $redirectUriParsed['scheme'] . '://' . $redirectUriParsed['host'] . $redirectUriParsed['path'];
|
||||||
|
$redirectUriQuery = [];
|
||||||
|
if (isset($redirectUriParsed['query'])) {
|
||||||
|
parse_str($redirectUriParsed['query'], $redirectUriQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($redirectUriBase, $client->getRedirectUrisArray())) {
|
||||||
|
return new HtmlContent('oauth/oauth_error', ['error' => 'Redirect URI \'' . $redirectUriBase .'\' is not allowed for this client.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ?User $user
|
||||||
|
*/
|
||||||
|
$user = \Container::$request->user();
|
||||||
|
$code = bin2hex(random_bytes(16));
|
||||||
|
$accessToken = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
$token = new OAuthToken();
|
||||||
|
$token->setNonce($nonce);
|
||||||
|
$token->setScope($scope);
|
||||||
|
$token->setUser($user);
|
||||||
|
$token->setCode($code);
|
||||||
|
$token->setAccessToken($accessToken);
|
||||||
|
$token->setCreatedDate(new DateTime());
|
||||||
|
$token->setExpiresDate(new DateTime('+5 minutes'));
|
||||||
|
\Container::$persistentDataManager->saveToDb($token);
|
||||||
|
|
||||||
|
$redirectUriQuery = array_merge($redirectUriQuery, [
|
||||||
|
'state' => $state,
|
||||||
|
'code' => $code
|
||||||
|
]);
|
||||||
|
$finalRedirectUri = $redirectUriBase . '?' . http_build_query($redirectUriQuery);
|
||||||
|
|
||||||
|
return new Redirect($finalRedirectUri, IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
}
|
@ -2,16 +2,9 @@
|
|||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use Firebase\JWT\JWT;
|
use Firebase\JWT\JWT;
|
||||||
use Firebase\JWT\Key;
|
|
||||||
use Firebase\JWT\SignatureInvalidException;
|
|
||||||
use Firebase\JWT\BeforeValidException;
|
|
||||||
use Firebase\JWT\ExpiredException;
|
|
||||||
use RVR\Repository\OAuthSessionRepository;
|
|
||||||
use RVR\Repository\OAuthTokenRepository;
|
use RVR\Repository\OAuthTokenRepository;
|
||||||
use RVR\Repository\UserRepository;
|
use RVR\Repository\UserRepository;
|
||||||
use RVR\PersistentData\Model\User;
|
use RVR\PersistentData\Model\User;
|
||||||
use RVR\PersistentData\Model\OAuthSession;
|
|
||||||
use RVR\PersistentData\Model\OAuthToken;
|
|
||||||
use RVR\Repository\OAuthClientRepository;
|
use RVR\Repository\OAuthClientRepository;
|
||||||
use SokoWeb\Interfaces\Response\IContent;
|
use SokoWeb\Interfaces\Response\IContent;
|
||||||
use SokoWeb\Response\JsonContent;
|
use SokoWeb\Response\JsonContent;
|
||||||
@ -20,8 +13,6 @@ class OAuthController
|
|||||||
{
|
{
|
||||||
private OAuthClientRepository $oAuthClientRepository;
|
private OAuthClientRepository $oAuthClientRepository;
|
||||||
|
|
||||||
private OAuthSessionRepository $oAuthSessionRepository;
|
|
||||||
|
|
||||||
private OAuthTokenRepository $oAuthTokenRepository;
|
private OAuthTokenRepository $oAuthTokenRepository;
|
||||||
|
|
||||||
private UserRepository $userRepository;
|
private UserRepository $userRepository;
|
||||||
@ -29,181 +20,60 @@ class OAuthController
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->oAuthClientRepository = new OAuthClientRepository();
|
$this->oAuthClientRepository = new OAuthClientRepository();
|
||||||
$this->oAuthSessionRepository = new OAuthSessionRepository();
|
|
||||||
$this->oAuthTokenRepository = new OAuthTokenRepository();
|
$this->oAuthTokenRepository = new OAuthTokenRepository();
|
||||||
$this->userRepository = new UserRepository();
|
$this->userRepository = new UserRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generateToken(): ?IContent
|
public function getToken(): ?IContent
|
||||||
{
|
{
|
||||||
$credentials = $this->getClientCredentials();
|
$clientId = \Container::$request->post('client_id');
|
||||||
|
$clientSecret = \Container::$request->post('client_secret');
|
||||||
$code = \Container::$request->post('code');
|
$code = \Container::$request->post('code');
|
||||||
$redirectUri = \Container::$request->post('redirect_uri');
|
|
||||||
|
|
||||||
if (!$credentials['clientId'] || !$code || !$redirectUri) {
|
if (!$clientId || !$clientSecret || !$code) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => 'An invalid request was made.'
|
'error' => 'An invalid request was made.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$client = $this->oAuthClientRepository->getByClientId($credentials['clientId']);
|
$client = $this->oAuthClientRepository->getByClientId($clientId);
|
||||||
if ($client === null) {
|
if ($client === null || $client->getClientSecret() !== $clientSecret) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => 'Client is not found.'
|
'error' => 'Client is not authorized.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$redirectUriBase = explode('?', $redirectUri)[0];
|
$token = $this->oAuthTokenRepository->getByCode($code);
|
||||||
if (!in_array($redirectUriBase, $client->getRedirectUrisArray())) {
|
if ($token === null || $token->getExpiresDate() < new DateTime()) {
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'Redirect URI \'' . $redirectUriBase .'\' is not allowed for this client.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$session = $this->oAuthSessionRepository->getByCode($code);
|
|
||||||
if ($session === null || $session->getTokenClaimed() || $session->getExpiresDate() < new DateTime()) {
|
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => 'The provided code is invalid.'
|
'error' => 'The provided code is invalid.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$codeChallenge = $session->getCodeChallenge();
|
$payload = array_merge([
|
||||||
if ($codeChallenge === null && $client->getClientSecret() !== $credentials['clientSecret']) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'Client is not authorized.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($session->getClientId() !== $credentials['clientId']) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'This code cannot be used by this client!'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($codeChallenge !== null) {
|
|
||||||
$codeVerifier = \Container::$request->post('code_verifier') ?: '';
|
|
||||||
if ($session->getCodeChallengeMethod() === 'S256') {
|
|
||||||
$hash = rtrim(strtr(base64_encode(hash('sha256', $codeVerifier, true)), '+/', '-_'), '=');
|
|
||||||
} else {
|
|
||||||
$hash = $codeVerifier;
|
|
||||||
}
|
|
||||||
if ($codeChallenge !== $hash) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'Code challenge failed!'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$session->setTokenClaimed(true);
|
|
||||||
\Container::$persistentDataManager->saveToDb($session);
|
|
||||||
|
|
||||||
$token = new OAuthToken();
|
|
||||||
$token->setSession($session);
|
|
||||||
$token->setCreatedDate(new DateTime());
|
|
||||||
$token->setExpiresDate(new DateTime('+1 hours'));
|
|
||||||
\Container::$persistentDataManager->saveToDb($token);
|
|
||||||
|
|
||||||
$commonPayload = [
|
|
||||||
'iss' => $_ENV['APP_URL'],
|
'iss' => $_ENV['APP_URL'],
|
||||||
'iat' => $token->getCreatedDate()->getTimestamp(),
|
'iat' => (int)$token->getCreatedDate()->getTimestamp(),
|
||||||
'nbf' => $session->getCreatedDate()->getTimestamp(),
|
'nbf' => (int)$token->getCreatedDate()->getTimestamp(),
|
||||||
'exp' => $token->getExpiresDate()->getTimestamp(),
|
'exp' => (int)$token->getExpiresDate()->getTimestamp(),
|
||||||
'aud' => $session->getClientId(),
|
'aud' => $clientId,
|
||||||
'nonce' => $session->getNonce()
|
'nonce' => $token->getNonce()
|
||||||
];
|
], $this->getUserInfoInternal(
|
||||||
$idTokenPayload = array_merge($commonPayload, $this->getUserInfoInternal(
|
$this->userRepository->getById($token->getUserId()),
|
||||||
$this->userRepository->getById($session->getUserId()),
|
$token->getScopeArray())
|
||||||
$session->getScopeArray())
|
|
||||||
);
|
);
|
||||||
$accessTokenPayload = array_merge($commonPayload, [
|
|
||||||
'jti' => $token->getId(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$privateKey = file_get_contents(ROOT . '/' . $_ENV['JWT_RSA_PRIVATE_KEY']);
|
$privateKey = file_get_contents(ROOT . '/' . $_ENV['JWT_RSA_PRIVATE_KEY']);
|
||||||
$idToken = JWT::encode($idTokenPayload, $privateKey, 'RS256', $_ENV['JWT_KEY_KID']);
|
$jwt = JWT::encode($payload, $privateKey, 'RS256');
|
||||||
$accessToken = JWT::encode($accessTokenPayload, $privateKey, 'RS256', $_ENV['JWT_KEY_KID']);
|
|
||||||
|
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'access_token' => $accessToken,
|
'access_token' => $token->getAccessToken(),
|
||||||
'expires_in' => $token->getExpiresDate()->getTimestamp() - (new DateTime())->getTimestamp(),
|
'expires_in' => $token->getExpiresDate()->getTimestamp() - (new DateTime())->getTimestamp(),
|
||||||
'scope' => $session->getScope(),
|
'scope' => $token->getScope(),
|
||||||
'id_token' => $idToken,
|
'id_token' => $jwt,
|
||||||
'token_type' => 'Bearer'
|
'token_type' => 'Bearer'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function introspectToken(): ?IContent
|
|
||||||
{
|
|
||||||
$credentials = $this->getClientCredentials();
|
|
||||||
$accessToken = \Container::$request->post('token');
|
|
||||||
|
|
||||||
if (!$credentials['clientId'] || !$credentials['clientSecret'] || !$accessToken) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'An invalid request was made.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$client = $this->oAuthClientRepository->getByClientId($credentials['clientId']);
|
|
||||||
if ($client === null || $client->getClientSecret() !== $credentials['clientSecret']) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'Client is not authorized.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$tokenValidated = $this->validateTokenAndSession($accessToken, $token, $session);
|
|
||||||
if (!$tokenValidated) {
|
|
||||||
return new JsonContent([
|
|
||||||
'active' => false
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($session->getClientId() !== $credentials['clientId']) {
|
|
||||||
return new JsonContent([
|
|
||||||
'active' => false
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonContent([
|
|
||||||
'active' => true,
|
|
||||||
'scope' => $session->getScope(),
|
|
||||||
'client_id' => $session->getClientId(),
|
|
||||||
'exp' => $token->getExpiresDate()->getTimestamp(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function revokeToken(): ?IContent
|
|
||||||
{
|
|
||||||
$credentials = $this->getClientCredentials();
|
|
||||||
$accessToken = \Container::$request->post('token');
|
|
||||||
|
|
||||||
if (!$credentials['clientId'] || !$credentials['clientSecret'] || !$accessToken) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'An invalid request was made.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$client = $this->oAuthClientRepository->getByClientId($credentials['clientId']);
|
|
||||||
if ($client === null || $client->getClientSecret() !== $credentials['clientSecret']) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => 'Client is not authorized.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$tokenValidated = $this->validateTokenAndSession($accessToken, $token, $session);
|
|
||||||
if (!$tokenValidated) {
|
|
||||||
return new JsonContent([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$session = $this->oAuthSessionRepository->getById($token->getSessionId());
|
|
||||||
if ($session->getClientId() !== $credentials['clientId']) {
|
|
||||||
return new JsonContent([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($token);
|
|
||||||
|
|
||||||
return new JsonContent([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserInfo() : IContent
|
public function getUserInfo() : IContent
|
||||||
{
|
{
|
||||||
$authorization = \Container::$request->header('Authorization');
|
$authorization = \Container::$request->header('Authorization');
|
||||||
@ -214,8 +84,9 @@ class OAuthController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$accessToken = substr($authorization, strlen('Bearer '));
|
$accessToken = substr($authorization, strlen('Bearer '));
|
||||||
$tokenValidated = $this->validateTokenAndSession($accessToken, $token, $session);
|
$token = $this->oAuthTokenRepository->getByAccessToken($accessToken);
|
||||||
if (!$tokenValidated) {
|
|
||||||
|
if ($token === null || $token->getExpiresDate() < new DateTime()) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => 'The provided access token is invalid.'
|
'error' => 'The provided access token is invalid.'
|
||||||
]);
|
]);
|
||||||
@ -223,8 +94,8 @@ class OAuthController
|
|||||||
|
|
||||||
return new JsonContent(
|
return new JsonContent(
|
||||||
$this->getUserInfoInternal(
|
$this->getUserInfoInternal(
|
||||||
$this->userRepository->getById($session->getUserId()),
|
$this->userRepository->getById($token->getUserId()),
|
||||||
$session->getScopeArray()
|
$token->getScopeArray()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -235,10 +106,7 @@ class OAuthController
|
|||||||
'issuer' => $_ENV['APP_URL'],
|
'issuer' => $_ENV['APP_URL'],
|
||||||
'authorization_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.auth')->generateLink(),
|
'authorization_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.auth')->generateLink(),
|
||||||
'token_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.token')->generateLink(),
|
'token_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.token')->generateLink(),
|
||||||
'introspection_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.token.introspect')->generateLink(),
|
|
||||||
'revocation_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.token.revoke')->generateLink(),
|
|
||||||
'userinfo_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.userinfo')->generateLink(),
|
'userinfo_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.userinfo')->generateLink(),
|
||||||
'end_session_endpoint' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('logout')->generateLink(),
|
|
||||||
'jwks_uri' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.certs')->generateLink(),
|
'jwks_uri' => \Container::$request->getBase() . \Container::$routeCollection->getRoute('oauth.certs')->generateLink(),
|
||||||
'response_types_supported' =>
|
'response_types_supported' =>
|
||||||
[
|
[
|
||||||
@ -260,7 +128,6 @@ class OAuthController
|
|||||||
],
|
],
|
||||||
'token_endpoint_auth_methods_supported' =>
|
'token_endpoint_auth_methods_supported' =>
|
||||||
[
|
[
|
||||||
'client_secret_basic',
|
|
||||||
'client_secret_post',
|
'client_secret_post',
|
||||||
],
|
],
|
||||||
'claims_supported' =>
|
'claims_supported' =>
|
||||||
@ -300,66 +167,13 @@ class OAuthController
|
|||||||
'kty' => 'RSA',
|
'kty' => 'RSA',
|
||||||
'alg' => 'RS256',
|
'alg' => 'RS256',
|
||||||
'use' => 'sig',
|
'use' => 'sig',
|
||||||
'kid' => $_ENV['JWT_KEY_KID'],
|
'kid' => '1',
|
||||||
'n' => str_replace(['+', '/'], ['-', '_'], base64_encode($keyInfo['rsa']['n'])),
|
'n' => str_replace(['+', '/'], ['-', '_'], base64_encode($keyInfo['rsa']['n'])),
|
||||||
'e' => str_replace(['+', '/'], ['-', '_'], base64_encode($keyInfo['rsa']['e'])),
|
'e' => str_replace(['+', '/'], ['-', '_'], base64_encode($keyInfo['rsa']['e'])),
|
||||||
]
|
]
|
||||||
]]);
|
]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getClientCredentials(): array
|
|
||||||
{
|
|
||||||
$authorization = \Container::$request->header('Authorization');
|
|
||||||
if ($authorization !== null) {
|
|
||||||
$basicAuthEncoded = substr($authorization, strlen('Basic '));
|
|
||||||
$basicAuth = explode(':', base64_decode($basicAuthEncoded));
|
|
||||||
if (count($basicAuth) === 2) {
|
|
||||||
$clientId = rawurldecode($basicAuth[0]);
|
|
||||||
$clientSecret = rawurldecode($basicAuth[1]);
|
|
||||||
} else {
|
|
||||||
$clientId = null;
|
|
||||||
$clientSecret = null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$clientId = \Container::$request->post('client_id');
|
|
||||||
$clientSecret = \Container::$request->post('client_secret');
|
|
||||||
}
|
|
||||||
return ['clientId' => $clientId, 'clientSecret' => $clientSecret];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function validateTokenAndSession(
|
|
||||||
string $accessToken,
|
|
||||||
?OAuthToken &$token,
|
|
||||||
?OAuthSession &$session): bool
|
|
||||||
{
|
|
||||||
$publicKey = file_get_contents(ROOT . '/' . $_ENV['JWT_RSA_PUBLIC_KEY']);
|
|
||||||
try {
|
|
||||||
$payload = JWT::decode($accessToken, new Key($publicKey, 'RS256'));
|
|
||||||
$token = $this->oAuthTokenRepository->getById($payload->jti);
|
|
||||||
} catch (SignatureInvalidException | BeforeValidException | ExpiredException) {
|
|
||||||
$token = null;
|
|
||||||
} catch (\UnexpectedValueException $e) {
|
|
||||||
error_log($e->getMessage() . ' Token was: ' . $accessToken);
|
|
||||||
$token = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($token === null || $token->getExpiresDate() < new DateTime()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$session = $this->oAuthSessionRepository->getById($token->getSessionId());
|
|
||||||
if ($session === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param User $user
|
|
||||||
* @param string[] $scope
|
|
||||||
* @return array<string, string>
|
|
||||||
*/
|
|
||||||
private function getUserInfoInternal(User $user, array $scope): array
|
private function getUserInfoInternal(User $user, array $scope): array
|
||||||
{
|
{
|
||||||
$userInfo = [];
|
$userInfo = [];
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
<?php namespace RVR\Controller;
|
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use RVR\PersistentData\Model\OAuthSession;
|
|
||||||
use RVR\PersistentData\Model\User;
|
|
||||||
use RVR\Repository\OAuthClientRepository;
|
|
||||||
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
|
||||||
use SokoWeb\Interfaces\Response\IRedirect;
|
|
||||||
use SokoWeb\Response\Redirect;
|
|
||||||
use SokoWeb\Response\HtmlContent;
|
|
||||||
|
|
||||||
class OAuthSessionController implements IAuthenticationRequired
|
|
||||||
{
|
|
||||||
private OAuthClientRepository $oAuthClientRepository;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->oAuthClientRepository = new OAuthClientRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isAuthenticationRequired(): bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function auth(): HtmlContent|Redirect
|
|
||||||
{
|
|
||||||
$redirectUri = \Container::$request->query('redirect_uri');
|
|
||||||
$clientId = \Container::$request->query('client_id');
|
|
||||||
$scope = \Container::$request->query('scope') ? \Container::$request->query('scope'): '';
|
|
||||||
$state = \Container::$request->query('state');
|
|
||||||
$nonce = \Container::$request->query('nonce') ? \Container::$request->query('nonce'): '';
|
|
||||||
$codeChallenge = \Container::$request->query('code_challenge') ?: null;
|
|
||||||
$codeChallengeMethod = \Container::$request->query('code_challenge_method') ?: null;
|
|
||||||
|
|
||||||
if (!$clientId || !$redirectUri || !$state || (!$codeChallenge && $codeChallengeMethod)) {
|
|
||||||
return new HtmlContent('oauth/oauth_error', ['error' => 'An invalid request was made. Please start authentication again.']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($codeChallenge && (strlen($codeChallenge) < 43 || strlen($codeChallenge) > 128)) {
|
|
||||||
return new HtmlContent('oauth/oauth_error', ['error' => 'Code challenge should be one between 43 and 128 characters long.']);
|
|
||||||
}
|
|
||||||
$possibleCodeChallengeMethods = ['plain', 'S256'];
|
|
||||||
if ($codeChallenge && !in_array($codeChallengeMethod, $possibleCodeChallengeMethods)) {
|
|
||||||
return new HtmlContent('oauth/oauth_error', ['error' => 'Code challenge method should be one of the following: ' . implode(',', $possibleCodeChallengeMethods)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$client = $this->oAuthClientRepository->getByClientId($clientId);
|
|
||||||
if ($client === null) {
|
|
||||||
return new HtmlContent('oauth/oauth_error', ['error' => 'Client is not found.']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$redirectUriQueryParsed = [];
|
|
||||||
if (str_contains('?', $redirectUri)) {
|
|
||||||
[$redirectUriBase, $redirectUriQuery] = explode('?', $redirectUri, 2);
|
|
||||||
parse_str($redirectUriQuery, $redirectUriQueryParsed);
|
|
||||||
} else {
|
|
||||||
$redirectUriBase = $redirectUri;
|
|
||||||
}
|
|
||||||
if (!in_array($redirectUriBase, $client->getRedirectUrisArray())) {
|
|
||||||
return new HtmlContent('oauth/oauth_error', ['error' => 'Redirect URI \'' . $redirectUriBase .'\' is not allowed for this client.']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ?User $user
|
|
||||||
*/
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
$code = bin2hex(random_bytes(16));
|
|
||||||
|
|
||||||
$session = new OAuthSession();
|
|
||||||
$session->setClientId($clientId);
|
|
||||||
$session->setNonce($nonce);
|
|
||||||
$session->setScope($scope);
|
|
||||||
$session->setCodeChallenge($codeChallenge);
|
|
||||||
$session->setCodeChallengeMethod($codeChallengeMethod);
|
|
||||||
$session->setUser($user);
|
|
||||||
$session->setCode($code);
|
|
||||||
$session->setCreatedDate(new DateTime());
|
|
||||||
$session->setExpiresDate(new DateTime('+5 minutes'));
|
|
||||||
\Container::$persistentDataManager->saveToDb($session);
|
|
||||||
|
|
||||||
$redirectUriQueryParsed = array_merge($redirectUriQueryParsed, [
|
|
||||||
'state' => $state,
|
|
||||||
'code' => $code
|
|
||||||
]);
|
|
||||||
$finalRedirectUri = $redirectUriBase . '?' . http_build_query($redirectUriQueryParsed);
|
|
||||||
|
|
||||||
return new Redirect($finalRedirectUri, IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,14 +6,11 @@ use RVR\Finance\ExchangeRateCalculator;
|
|||||||
use RVR\PersistentData\Model\Community;
|
use RVR\PersistentData\Model\Community;
|
||||||
use RVR\PersistentData\Model\CommunityMember;
|
use RVR\PersistentData\Model\CommunityMember;
|
||||||
use RVR\PersistentData\Model\Transaction;
|
use RVR\PersistentData\Model\Transaction;
|
||||||
use RVR\PersistentData\Model\TransactionPayee;
|
|
||||||
use RVR\PersistentData\Model\User;
|
use RVR\PersistentData\Model\User;
|
||||||
use RVR\Repository\CommunityMemberRepository;
|
use RVR\Repository\CommunityMemberRepository;
|
||||||
use RVR\Repository\CommunityRepository;
|
use RVR\Repository\CommunityRepository;
|
||||||
use RVR\Repository\CurrencyRepository;
|
use RVR\Repository\CurrencyRepository;
|
||||||
use RVR\Repository\TransactionRepository;
|
use RVR\Repository\TransactionRepository;
|
||||||
use RVR\Repository\TransactionPayeeRepository;
|
|
||||||
use RVR\Repository\EventRepository;
|
|
||||||
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
||||||
use SokoWeb\Interfaces\Authorization\ISecured;
|
use SokoWeb\Interfaces\Authorization\ISecured;
|
||||||
use SokoWeb\Interfaces\Response\IContent;
|
use SokoWeb\Interfaces\Response\IContent;
|
||||||
@ -30,13 +27,9 @@ class TransactionController implements IAuthenticationRequired, ISecured
|
|||||||
|
|
||||||
private TransactionRepository $transactionRepository;
|
private TransactionRepository $transactionRepository;
|
||||||
|
|
||||||
private TransactionPayeeRepository $transactionPayeeRepository;
|
private Community $community;
|
||||||
|
|
||||||
private EventRepository $eventRepository;
|
private CommunityMember $ownCommunityMember;
|
||||||
|
|
||||||
private ?Community $community;
|
|
||||||
|
|
||||||
private ?CommunityMember $ownCommunityMember;
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@ -44,8 +37,6 @@ class TransactionController implements IAuthenticationRequired, ISecured
|
|||||||
$this->communityMemberRepository = new CommunityMemberRepository();
|
$this->communityMemberRepository = new CommunityMemberRepository();
|
||||||
$this->currencyRepository = new CurrencyRepository();
|
$this->currencyRepository = new CurrencyRepository();
|
||||||
$this->transactionRepository = new TransactionRepository();
|
$this->transactionRepository = new TransactionRepository();
|
||||||
$this->transactionPayeeRepository = new TransactionPayeeRepository();
|
|
||||||
$this->eventRepository = new EventRepository();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAuthenticationRequired(): bool
|
public function isAuthenticationRequired(): bool
|
||||||
@ -55,8 +46,8 @@ class TransactionController implements IAuthenticationRequired, ISecured
|
|||||||
|
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
$communitySlug = \Container::$request->query('communitySlug');
|
$communityId = \Container::$request->query('communityId');
|
||||||
$this->community = $this->communityRepository->getBySlug($communitySlug);
|
$this->community = $this->communityRepository->getById($communityId);
|
||||||
if ($this->community === null) {
|
if ($this->community === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -78,46 +69,25 @@ class TransactionController implements IAuthenticationRequired, ISecured
|
|||||||
Container::$persistentDataManager->loadRelationsFromDb($this->community, true, ['main_currency']);
|
Container::$persistentDataManager->loadRelationsFromDb($this->community, true, ['main_currency']);
|
||||||
$exchangeRateCalculator = new ExchangeRateCalculator($this->community->getMainCurrency());
|
$exchangeRateCalculator = new ExchangeRateCalculator($this->community->getMainCurrency());
|
||||||
|
|
||||||
$eventSlug = Container::$request->query('event');
|
|
||||||
if ($eventSlug) {
|
|
||||||
$event = $this->eventRepository->getBySlug($eventSlug);
|
|
||||||
} else {
|
|
||||||
$event = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$itemsPerPage = 50;
|
$itemsPerPage = 50;
|
||||||
$numberOfTransactions = $event ?
|
$numberOfTransactions = $this->transactionRepository->countAllByCommunity($this->community);
|
||||||
$this->transactionRepository->countAllByEvent($event) :
|
$pages = ceil($numberOfTransactions / $itemsPerPage);
|
||||||
$this->transactionRepository->countAllByCommunity($this->community);
|
$currentPage = Container::$request->query('page') ?: 0;
|
||||||
$currentPage = Container::$request->query('page') ?: 1;
|
$transactions = $this->transactionRepository->getPagedByCommunity(
|
||||||
$transactions = $event ?
|
|
||||||
$this->transactionRepository->getPagedByEvent(
|
|
||||||
$event,
|
|
||||||
$currentPage,
|
|
||||||
$itemsPerPage,
|
|
||||||
true,
|
|
||||||
['currency', 'payer_user']
|
|
||||||
) :
|
|
||||||
$this->transactionRepository->getPagedByCommunity(
|
|
||||||
$this->community,
|
$this->community,
|
||||||
$currentPage,
|
$currentPage * $itemsPerPage,
|
||||||
$itemsPerPage,
|
$itemsPerPage,
|
||||||
true,
|
true,
|
||||||
['event', 'currency', 'payer_user']
|
['currency', 'payer_user', 'payee_user']
|
||||||
);
|
);
|
||||||
|
|
||||||
$transactions = iterator_to_array($transactions);
|
|
||||||
Container::$persistentDataManager->loadMultiRelationsFromDb($transactions, 'payees', true, ['user']);
|
|
||||||
|
|
||||||
return new HtmlContent('communities/transactions', [
|
return new HtmlContent('communities/transactions', [
|
||||||
'community' => $this->community,
|
'community' => $this->community,
|
||||||
'event' => $event,
|
|
||||||
'exchangeRateCalculator' => $exchangeRateCalculator,
|
'exchangeRateCalculator' => $exchangeRateCalculator,
|
||||||
'pages' => ceil($numberOfTransactions / $itemsPerPage),
|
'pages' => $pages,
|
||||||
'currentPage' => $currentPage,
|
'currentPage' => $currentPage,
|
||||||
'numberOfTransactions' => $numberOfTransactions,
|
'numberOfTransactions' => $numberOfTransactions,
|
||||||
'transactions' => $transactions,
|
'transactions' => $transactions
|
||||||
'members' => $this->getMembers($this->community)
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,28 +99,13 @@ class TransactionController implements IAuthenticationRequired, ISecured
|
|||||||
if ($transaction === null) {
|
if ($transaction === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Container::$persistentDataManager->loadRelationsFromDb($transaction, false, ['event']);
|
|
||||||
$event = $transaction->getEvent();
|
|
||||||
$payeeUserIds = [];
|
|
||||||
foreach ($this->transactionPayeeRepository->getAllByTransaction($transaction) as $payee) {
|
|
||||||
$payeeUserIds[] = $payee->getUserId();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
$transaction = null;
|
$transaction = null;
|
||||||
$eventSlug = Container::$request->query('event');
|
|
||||||
if ($eventSlug) {
|
|
||||||
$event = $this->eventRepository->getBySlug($eventSlug);
|
|
||||||
} else {
|
|
||||||
$event = null;
|
|
||||||
}
|
|
||||||
$payeeUserIds = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HtmlContent('communities/transaction_edit', [
|
return new HtmlContent('communities/transaction_edit', [
|
||||||
'community' => $this->community,
|
'community' => $this->community,
|
||||||
'transaction' => $transaction,
|
'transaction' => $transaction,
|
||||||
'payeeUserIds' => $payeeUserIds,
|
|
||||||
'event' => $event,
|
|
||||||
'members' => $this->getMembers($this->community),
|
'members' => $this->getMembers($this->community),
|
||||||
'currencies' => $this->getCurrencies($this->community)
|
'currencies' => $this->getCurrencies($this->community)
|
||||||
]);
|
]);
|
||||||
@ -166,51 +121,20 @@ class TransactionController implements IAuthenticationRequired, ISecured
|
|||||||
$transaction->setCommunity($this->community);
|
$transaction->setCommunity($this->community);
|
||||||
}
|
}
|
||||||
|
|
||||||
$transaction->setEventId(Container::$request->post('event_id') ?: null);
|
|
||||||
$transaction->setCurrencyId(Container::$request->post('currency_id'));
|
$transaction->setCurrencyId(Container::$request->post('currency_id'));
|
||||||
$transaction->setPayerUserId(Container::$request->post('payer_user_id'));
|
$transaction->setPayerUserId(Container::$request->post('payer_user_id'));
|
||||||
|
$transaction->setPayeeUserId(Container::$request->post('payee_user_id') ?: null);
|
||||||
$transaction->setDescription(Container::$request->post('description'));
|
$transaction->setDescription(Container::$request->post('description'));
|
||||||
$transaction->setSum(Container::$request->post('sum'));
|
$transaction->setSum(Container::$request->post('sum'));
|
||||||
$transaction->setTimeDate(new DateTime(Container::$request->post('time')));
|
$transaction->setTimeDate(new DateTime(Container::$request->post('time')));
|
||||||
Container::$persistentDataManager->saveToDb($transaction);
|
Container::$persistentDataManager->saveToDb($transaction);
|
||||||
|
|
||||||
$payeeUserIds = array_unique(Container::$request->post('payee_user_ids'));
|
|
||||||
if (count($payeeUserIds) === $this->communityMemberRepository->countAllByCommunity($this->community)) {
|
|
||||||
$payeeUserIds = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$currentPayees = [];
|
|
||||||
foreach ($payeeUserIds as $payeeUserId) {
|
|
||||||
$payee = new TransactionPayee();
|
|
||||||
$payee->setTransaction($transaction);
|
|
||||||
$payee->setUserId((int)$payeeUserId);
|
|
||||||
$currentPayees[(int)$payeeUserId] = $payee;
|
|
||||||
}
|
|
||||||
$existingPayees = [];
|
|
||||||
if ($transactionId) {
|
|
||||||
foreach ($this->transactionPayeeRepository->getAllByTransaction($transaction) as $payee) {
|
|
||||||
$existingPayees[$payee->getUserId()] = $payee;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (array_diff_key($currentPayees, $existingPayees) as $newPayee) {
|
|
||||||
Container::$persistentDataManager->saveToDb($newPayee);
|
|
||||||
}
|
|
||||||
foreach (array_diff_key($existingPayees, $currentPayees) as $deletedPayee) {
|
|
||||||
Container::$persistentDataManager->deleteFromDb($deletedPayee);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteTransaction(): IContent
|
public function deleteTransaction(): IContent
|
||||||
{
|
{
|
||||||
$transaction = $this->transactionRepository->getById(Container::$request->query('transactionId'));
|
$transaction = $this->transactionRepository->getById(Container::$request->query('transactionId'));
|
||||||
|
|
||||||
foreach ($this->transactionPayeeRepository->getAllByTransaction($transaction) as $payee) {
|
|
||||||
Container::$persistentDataManager->deleteFromDb($payee);
|
|
||||||
}
|
|
||||||
|
|
||||||
Container::$persistentDataManager->deleteFromDb($transaction);
|
Container::$persistentDataManager->deleteFromDb($transaction);
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
@ -219,7 +143,7 @@ class TransactionController implements IAuthenticationRequired, ISecured
|
|||||||
private function getMembers(Community $community): array
|
private function getMembers(Community $community): array
|
||||||
{
|
{
|
||||||
$members = iterator_to_array($this->communityMemberRepository->getAllByCommunity($community, true, ['user']));
|
$members = iterator_to_array($this->communityMemberRepository->getAllByCommunity($community, true, ['user']));
|
||||||
usort($members, function ($a, $b) {
|
usort($members, function($a, $b) {
|
||||||
return strnatcmp($a->getUser()->getDisplayName(), $b->getUser()->getDisplayName());
|
return strnatcmp($a->getUser()->getDisplayName(), $b->getUser()->getDisplayName());
|
||||||
});
|
});
|
||||||
return $members;
|
return $members;
|
||||||
@ -228,10 +152,10 @@ class TransactionController implements IAuthenticationRequired, ISecured
|
|||||||
private function getCurrencies(Community $community): array
|
private function getCurrencies(Community $community): array
|
||||||
{
|
{
|
||||||
$currencies = iterator_to_array($this->currencyRepository->getAllByCommunity($community));
|
$currencies = iterator_to_array($this->currencyRepository->getAllByCommunity($community));
|
||||||
usort($currencies, function ($a, $b) {
|
usort($currencies, function($a, $b) {
|
||||||
return strnatcmp($a->getCode(), $b->getCode());
|
return strnatcmp($a->getCode(), $b->getCode());
|
||||||
});
|
});
|
||||||
usort($currencies, function ($a, $b) use ($community) {
|
usort($currencies, function($a, $b) use ($community) {
|
||||||
return (int)($b->getId() === $community->getMainCurrencyId()) - (int)($a->getId() === $community->getMainCurrencyId());
|
return (int)($b->getId() === $community->getMainCurrencyId()) - (int)($a->getId() === $community->getMainCurrencyId());
|
||||||
});
|
});
|
||||||
return $currencies;
|
return $currencies;
|
||||||
|
@ -37,130 +37,6 @@ class UserController implements IAuthenticationRequired
|
|||||||
return new HtmlContent('account/account', ['user' => $user->toArray()]);
|
return new HtmlContent('account/account', ['user' => $user->toArray()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGoogleConnectRedirect(): IRedirect
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
|
|
||||||
$state = bin2hex(random_bytes(16));
|
|
||||||
$nonce = bin2hex(random_bytes(16));
|
|
||||||
|
|
||||||
\Container::$request->session()->set('oauth_state', $state);
|
|
||||||
\Container::$request->session()->set('oauth_nonce', $nonce);
|
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth(new Request());
|
|
||||||
|
|
||||||
$url = $oAuth->getDialogUrl(
|
|
||||||
$state,
|
|
||||||
\Container::$request->getBase() . \Container::$routeCollection->getRoute('account.googleConnect-confirm')->generateLink(),
|
|
||||||
$nonce,
|
|
||||||
$user->getEmail()
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Redirect($url, IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGoogleConnectConfirm(): IContent
|
|
||||||
{
|
|
||||||
$defaultError = 'Authentication with Google failed. Please <a href="' . \Container::$routeCollection->getRoute('account.googleConnect')->generateLink() . '" title="Connect with Google">try again</a>!';
|
|
||||||
|
|
||||||
if (\Container::$request->query('state') !== \Container::$request->session()->get('oauth_state')) {
|
|
||||||
return new HtmlContent('account/google_connect', ['success' => false, 'error' => $defaultError]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth(new Request());
|
|
||||||
$tokenData = $oAuth->getToken(
|
|
||||||
\Container::$request->query('code'),
|
|
||||||
\Container::$request->getBase() . \Container::$routeCollection->getRoute('account.googleConnect-confirm')->generateLink()
|
|
||||||
);
|
|
||||||
if (!isset($tokenData['id_token'])) {
|
|
||||||
return new HtmlContent('account/google_connect', ['success' => false, 'error' => $defaultError]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$jwtParser = new JwtParser($tokenData['id_token']);
|
|
||||||
$idToken = $jwtParser->getPayload();
|
|
||||||
if ($idToken['nonce'] !== \Container::$request->session()->get('oauth_nonce')) {
|
|
||||||
return new HtmlContent('account/google_connect', ['success' => false, 'error' => $defaultError]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$anotherUser = $this->userRepository->getByGoogleSub($idToken['sub']);
|
|
||||||
if ($anotherUser !== null) {
|
|
||||||
return new HtmlContent('account/google_connect', [
|
|
||||||
'success' => false,
|
|
||||||
'error' => 'This Google account is linked to another account.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$request->session()->set('google_user_data', $idToken);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
|
|
||||||
return new HtmlContent('account/google_connect', [
|
|
||||||
'success' => true,
|
|
||||||
'googleAccount' => $idToken['email'],
|
|
||||||
'userEmail' => $user->getEmail()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function connectGoogle(): IContent
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
if (!$user->checkPassword(\Container::$request->post('password'))) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => [
|
|
||||||
'errorText' => 'The given password is wrong.'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$googleUserData = \Container::$request->session()->get('google_user_data');
|
|
||||||
$user->setGoogleSub($googleUserData['sub']);
|
|
||||||
\Container::$persistentDataManager->saveToDb($user);
|
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGoogleDisconnectConfirm(): IContent
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
|
|
||||||
return new HtmlContent('account/google_disconnect', [
|
|
||||||
'success' => true,
|
|
||||||
'userEmail' => $user->getEmail()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function disconnectGoogle(): IContent
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
if (!$user->checkPassword(\Container::$request->post('password'))) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => [
|
|
||||||
'errorText' => 'The given password is wrong.'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$user->setGoogleSub(null);
|
|
||||||
\Container::$persistentDataManager->saveToDb($user);
|
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGoogleAuthenticateRedirect(): IRedirect
|
public function getGoogleAuthenticateRedirect(): IRedirect
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
<?php namespace RVR\Finance;
|
<?php namespace RVR\Finance;
|
||||||
|
|
||||||
use Container;
|
|
||||||
use RVR\PersistentData\Model\Community;
|
use RVR\PersistentData\Model\Community;
|
||||||
use RVR\PersistentData\Model\Event;
|
|
||||||
use RVR\PersistentData\Model\User;
|
|
||||||
use RVR\Repository\CommunityMemberRepository;
|
use RVR\Repository\CommunityMemberRepository;
|
||||||
use RVR\Repository\TransactionRepository;
|
use RVR\Repository\TransactionRepository;
|
||||||
|
|
||||||
@ -11,10 +8,6 @@ class BalanceCalculator
|
|||||||
{
|
{
|
||||||
private Community $community;
|
private Community $community;
|
||||||
|
|
||||||
private User $user;
|
|
||||||
|
|
||||||
private ?Event $event;
|
|
||||||
|
|
||||||
private TransactionRepository $transactionRepository;
|
private TransactionRepository $transactionRepository;
|
||||||
|
|
||||||
private CommunityMemberRepository $communityMemberRepository;
|
private CommunityMemberRepository $communityMemberRepository;
|
||||||
@ -25,13 +18,9 @@ class BalanceCalculator
|
|||||||
|
|
||||||
private array $payments;
|
private array $payments;
|
||||||
|
|
||||||
private array $actualDebts;
|
public function __construct(Community $community)
|
||||||
|
|
||||||
public function __construct(Community $community, User $user, ?Event $event = null)
|
|
||||||
{
|
{
|
||||||
$this->community = $community;
|
$this->community = $community;
|
||||||
$this->user = $user;
|
|
||||||
$this->event = $event;
|
|
||||||
$this->transactionRepository = new TransactionRepository();
|
$this->transactionRepository = new TransactionRepository();
|
||||||
$this->communityMemberRepository = new CommunityMemberRepository();
|
$this->communityMemberRepository = new CommunityMemberRepository();
|
||||||
$this->exchangeRateCalculator = new ExchangeRateCalculator($this->community->getMainCurrency());
|
$this->exchangeRateCalculator = new ExchangeRateCalculator($this->community->getMainCurrency());
|
||||||
@ -42,8 +31,7 @@ class BalanceCalculator
|
|||||||
$this->collectMembers();
|
$this->collectMembers();
|
||||||
$this->createPaymentsMatrix();
|
$this->createPaymentsMatrix();
|
||||||
$this->sumTransactions();
|
$this->sumTransactions();
|
||||||
$this->calculateActualDebts();
|
return $this->calculateActualDebts();
|
||||||
return $this->calculateBalanceForUser();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function collectMembers(): void
|
private function collectMembers(): void
|
||||||
@ -68,22 +56,13 @@ class BalanceCalculator
|
|||||||
private function sumTransactions(): void
|
private function sumTransactions(): void
|
||||||
{
|
{
|
||||||
$membersCount = count($this->members);
|
$membersCount = count($this->members);
|
||||||
if ($this->event !== null) {
|
$transactions = $this->transactionRepository->getAllByCommunity($this->community, true, ['currency']);
|
||||||
$transactions = iterator_to_array($this->transactionRepository->getAllByEvent($this->event, true, ['currency']));
|
|
||||||
} else {
|
|
||||||
$transactions = iterator_to_array($this->transactionRepository->getAllByCommunity($this->community, true, ['currency']));
|
|
||||||
}
|
|
||||||
Container::$persistentDataManager->loadMultiRelationsFromDb($transactions, 'payees');
|
|
||||||
|
|
||||||
foreach ($transactions as $transaction) {
|
foreach ($transactions as $transaction) {
|
||||||
$sum = $this->exchangeRateCalculator->calculate($transaction->getSum(), $transaction->getCurrency(), $transaction->getTimeDate());
|
$sum = $this->exchangeRateCalculator->calculate($transaction->getSum(), $transaction->getCurrency(), $transaction->getTimeDate());
|
||||||
$payees = $transaction->getPayees();
|
|
||||||
$payeeCount = count($payees);
|
|
||||||
|
|
||||||
if ($payeeCount > 0) {
|
if ($transaction->getPayeeUserId()) {
|
||||||
foreach ($payees as $payee) {
|
$this->payments[$transaction->getPayerUserId()][$transaction->getPayeeUserId()] += $sum;
|
||||||
$this->payments[$transaction->getPayerUserId()][$payee->getUserId()] += $sum / $payeeCount;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
foreach ($this->members as $payeeUserId => $member) {
|
foreach ($this->members as $payeeUserId => $member) {
|
||||||
$this->payments[$transaction->getPayerUserId()][$payeeUserId] += $sum / $membersCount;
|
$this->payments[$transaction->getPayerUserId()][$payeeUserId] += $sum / $membersCount;
|
||||||
@ -92,45 +71,20 @@ class BalanceCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function calculateActualDebts(): void
|
private function calculateActualDebts(): array
|
||||||
{
|
{
|
||||||
$this->actualDebts = [];
|
$actualDebts = [];
|
||||||
|
|
||||||
foreach ($this->payments as $payerUserId => $paymentsOfPayer) {
|
foreach ($this->payments as $payerUserId => $paymentsOfPayer) {
|
||||||
foreach ($paymentsOfPayer as $payeeUserId => $sum) {
|
foreach ($paymentsOfPayer as $payeeUserId => $sum) {
|
||||||
$actualDebt = $this->payments[$payeeUserId][$payerUserId] - $sum;
|
$actualDebt = $this->payments[$payeeUserId][$payerUserId] - $sum;
|
||||||
|
|
||||||
if (round($actualDebt, $this->community->getMainCurrency()->getRoundDigits()) > 0.0) {
|
if (round($actualDebt, $this->community->getMainCurrency()->getRoundDigits()) > 0.0) {
|
||||||
$this->actualDebts[] = ['payer' => $this->members[$payerUserId], 'payee' => $this->members[$payeeUserId], 'amount' => $actualDebt];
|
$actualDebts[] = ['payer' => $this->members[$payerUserId], 'payee' => $this->members[$payeeUserId], 'amount' => $actualDebt];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function calculateBalanceForUser(): array
|
return $actualDebts;
|
||||||
{
|
|
||||||
$debtItems = [];
|
|
||||||
$debtBalance = 0.0;
|
|
||||||
$outstandingItems = [];
|
|
||||||
$outstandingBalance = 0.0;
|
|
||||||
foreach ($this->actualDebts as $debt) {
|
|
||||||
if ($debt['payer']->getId() === $this->user->getId()) {
|
|
||||||
$debtBalance += $debt['amount'];
|
|
||||||
$debtItems[] = $debt;
|
|
||||||
}
|
|
||||||
if ($debt['payee']->getId() === $this->user->getId()) {
|
|
||||||
$outstandingBalance += $debt['amount'];
|
|
||||||
$outstandingItems[] = $debt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$absoluteBalance = $outstandingBalance - $debtBalance;
|
|
||||||
|
|
||||||
return [
|
|
||||||
'absoluteBalance' => $absoluteBalance,
|
|
||||||
'debtItems' => $debtItems,
|
|
||||||
'debtBalance' => $debtBalance,
|
|
||||||
'outstandingItems' => $outstandingItems,
|
|
||||||
'outstandingBalance' => $outstandingBalance
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ class ExchangeRateCalculator
|
|||||||
|
|
||||||
$currentExchangeRate = null;
|
$currentExchangeRate = null;
|
||||||
foreach ($this->exchangeRates[$currency->getId()] as $exchangeRate) {
|
foreach ($this->exchangeRates[$currency->getId()] as $exchangeRate) {
|
||||||
if ($exchangeRate->getValidFromDate() > $time) {
|
if ($exchangeRate->getValidFrom() > $time) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$currentExchangeRate = $exchangeRate;
|
$currentExchangeRate = $exchangeRate;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<?php namespace RVR\PersistentData\Model;
|
<?php namespace RVR\PersistentData\Model;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use SokoWeb\PersistentData\Model\ModelWithSlug;
|
use SokoWeb\PersistentData\Model\Model;
|
||||||
|
|
||||||
class Community extends ModelWithSlug
|
class Community extends Model
|
||||||
{
|
{
|
||||||
protected static string $table = 'communities';
|
protected static string $table = 'communities';
|
||||||
|
|
||||||
@ -11,8 +11,6 @@ class Community extends ModelWithSlug
|
|||||||
|
|
||||||
protected static array $relations = ['main_currency' => Currency::class];
|
protected static array $relations = ['main_currency' => Currency::class];
|
||||||
|
|
||||||
protected static string $slugSource = 'name';
|
|
||||||
|
|
||||||
private string $name = '';
|
private string $name = '';
|
||||||
|
|
||||||
private string $currency = '';
|
private string $currency = '';
|
||||||
@ -33,12 +31,12 @@ class Community extends ModelWithSlug
|
|||||||
$this->currency = $currency;
|
$this->currency = $currency;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setMainCurrency(?Currency $mainCurrency): void
|
public function setMainCurrency(Currency $mainCurrency): void
|
||||||
{
|
{
|
||||||
$this->mainCurrency = $mainCurrency;
|
$this->mainCurrency = $mainCurrency;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setMainCurrencyId(?int $mainCurrencyId): void
|
public function setMainCurrencyId(int $mainCurrencyId): void
|
||||||
{
|
{
|
||||||
$this->mainCurrencyId = $mainCurrencyId;
|
$this->mainCurrencyId = $mainCurrencyId;
|
||||||
}
|
}
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
<?php namespace RVR\PersistentData\Model;
|
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use SokoWeb\PersistentData\Model\ModelWithSlug;
|
|
||||||
|
|
||||||
class Event extends ModelWithSlug
|
|
||||||
{
|
|
||||||
protected static string $table = 'events';
|
|
||||||
|
|
||||||
protected static array $fields = ['community_id', 'start', 'end', 'title', 'description'];
|
|
||||||
|
|
||||||
protected static array $relations = ['community' => Community::class];
|
|
||||||
|
|
||||||
protected static string $slugSource = 'title';
|
|
||||||
|
|
||||||
private ?Community $community = null;
|
|
||||||
|
|
||||||
private int $communityId;
|
|
||||||
|
|
||||||
private DateTime $start;
|
|
||||||
|
|
||||||
private DateTime $end;
|
|
||||||
|
|
||||||
private string $title = '';
|
|
||||||
|
|
||||||
private string $description = '';
|
|
||||||
|
|
||||||
public function setCommunity(Community $community): void
|
|
||||||
{
|
|
||||||
$this->community = $community;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCommunityId(int $communityId): void
|
|
||||||
{
|
|
||||||
$this->communityId = $communityId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setStartDate(DateTime $start): void
|
|
||||||
{
|
|
||||||
$this->start = $start;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setStart(string $start): void
|
|
||||||
{
|
|
||||||
$this->start = new DateTime($start);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setEndDate(DateTime $end): void
|
|
||||||
{
|
|
||||||
$this->end = $end;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setEnd(string $end): void
|
|
||||||
{
|
|
||||||
$this->end = new DateTime($end);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTitle(string $title): void
|
|
||||||
{
|
|
||||||
$this->title = $title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDescription(string $description): void
|
|
||||||
{
|
|
||||||
$this->description = $description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCommunity(): ?Community
|
|
||||||
{
|
|
||||||
return $this->community;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCommunityId(): int
|
|
||||||
{
|
|
||||||
return $this->communityId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getStartDate(): DateTime
|
|
||||||
{
|
|
||||||
return $this->start;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getStart(): string
|
|
||||||
{
|
|
||||||
return $this->start->format('Y-m-d H:i:s');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEndDate(): DateTime
|
|
||||||
{
|
|
||||||
return $this->end;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEnd(): string
|
|
||||||
{
|
|
||||||
return $this->end->format('Y-m-d H:i:s');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTitle(): string
|
|
||||||
{
|
|
||||||
return $this->title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return $this->description;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,182 +0,0 @@
|
|||||||
<?php namespace RVR\PersistentData\Model;
|
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use SokoWeb\PersistentData\Model\Model;
|
|
||||||
|
|
||||||
class OAuthSession extends Model
|
|
||||||
{
|
|
||||||
protected static string $table = 'oauth_sessions';
|
|
||||||
|
|
||||||
protected static array $fields = ['client_id', 'scope', 'nonce', 'code_challenge', 'code_challenge_method', 'user_id', 'code', 'created', 'expires', 'token_claimed'];
|
|
||||||
|
|
||||||
protected static array $relations = ['user' => User::class];
|
|
||||||
|
|
||||||
private static array $possibleScopeValues = ['openid', 'email', 'profile', 'union_profile'];
|
|
||||||
|
|
||||||
private static array $possibleCodeChallengeMethodValues = ['plain', 'S256'];
|
|
||||||
|
|
||||||
private string $clientId = '';
|
|
||||||
|
|
||||||
private array $scope = [];
|
|
||||||
|
|
||||||
private string $nonce = '';
|
|
||||||
|
|
||||||
private ?string $codeChallenge = null;
|
|
||||||
|
|
||||||
private ?string $codeChallengeMethod = null;
|
|
||||||
|
|
||||||
private ?User $user = null;
|
|
||||||
|
|
||||||
private ?int $userId = null;
|
|
||||||
|
|
||||||
private string $code = '';
|
|
||||||
|
|
||||||
private DateTime $created;
|
|
||||||
|
|
||||||
private DateTime $expires;
|
|
||||||
|
|
||||||
private bool $tokenClaimed = false;
|
|
||||||
|
|
||||||
public function setScopeArray(array $scope): void
|
|
||||||
{
|
|
||||||
$this->scope = array_intersect($scope, self::$possibleScopeValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setClientId(string $clientId): void
|
|
||||||
{
|
|
||||||
$this->clientId = $clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setScope(string $scope): void
|
|
||||||
{
|
|
||||||
$this->setScopeArray(explode(' ', $scope));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setNonce(string $nonce): void
|
|
||||||
{
|
|
||||||
$this->nonce = $nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCodeChallenge(?string $codeChallenge): void
|
|
||||||
{
|
|
||||||
$this->codeChallenge = $codeChallenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCodeChallengeMethod(?string $codeChallengeMethod): void
|
|
||||||
{
|
|
||||||
if ($codeChallengeMethod !== null && !in_array($codeChallengeMethod, self::$possibleCodeChallengeMethodValues)) {
|
|
||||||
throw new \UnexpectedValueException($codeChallengeMethod . ' is not possible for challengeMethod!');
|
|
||||||
}
|
|
||||||
$this->codeChallengeMethod = $codeChallengeMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUser(User $user): void
|
|
||||||
{
|
|
||||||
$this->user = $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUserId(int $userId): void
|
|
||||||
{
|
|
||||||
$this->userId = $userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCode(string $code): void
|
|
||||||
{
|
|
||||||
$this->code = $code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCreatedDate(DateTime $created): void
|
|
||||||
{
|
|
||||||
$this->created = $created;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setExpiresDate(DateTime $expires): void
|
|
||||||
{
|
|
||||||
$this->expires = $expires;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCreated(string $created): void
|
|
||||||
{
|
|
||||||
$this->created = new DateTime($created);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setExpires(string $expires): void
|
|
||||||
{
|
|
||||||
$this->expires = new DateTime($expires);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTokenClaimed(bool $tokenClaimed): void
|
|
||||||
{
|
|
||||||
$this->tokenClaimed = $tokenClaimed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getClientId(): string
|
|
||||||
{
|
|
||||||
return $this->clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getScope(): string
|
|
||||||
{
|
|
||||||
return implode(' ', $this->scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getScopeArray(): array
|
|
||||||
{
|
|
||||||
return $this->scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNonce(): string
|
|
||||||
{
|
|
||||||
return $this->nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCodeChallenge(): ?string
|
|
||||||
{
|
|
||||||
return $this->codeChallenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCodeChallengeMethod(): ?string
|
|
||||||
{
|
|
||||||
return $this->codeChallengeMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUser(): ?User
|
|
||||||
{
|
|
||||||
return $this->user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserId(): ?int
|
|
||||||
{
|
|
||||||
return $this->userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCode(): string
|
|
||||||
{
|
|
||||||
return $this->code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreatedDate(): DateTime
|
|
||||||
{
|
|
||||||
return $this->created;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreated(): string
|
|
||||||
{
|
|
||||||
return $this->created->format('Y-m-d H:i:s');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getExpiresDate(): DateTime
|
|
||||||
{
|
|
||||||
return $this->expires;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getExpires(): string
|
|
||||||
{
|
|
||||||
return $this->expires->format('Y-m-d H:i:s');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTokenClaimed(): bool
|
|
||||||
{
|
|
||||||
return $this->tokenClaimed;
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,26 +7,61 @@ class OAuthToken extends Model
|
|||||||
{
|
{
|
||||||
protected static string $table = 'oauth_tokens';
|
protected static string $table = 'oauth_tokens';
|
||||||
|
|
||||||
protected static array $fields = ['session_id', 'created', 'expires'];
|
protected static array $fields = ['scope', 'nonce', 'user_id', 'code', 'access_token', 'created', 'expires'];
|
||||||
|
|
||||||
protected static array $relations = ['session' => OAuthSession::class];
|
protected static array $relations = ['user' => User::class];
|
||||||
|
|
||||||
private ?OAuthSession $session = null;
|
private static array $possibleScopeValues = ['openid', 'email', 'profile'];
|
||||||
|
|
||||||
private ?int $sessionId = null;
|
private array $scope = [];
|
||||||
|
|
||||||
|
private string $nonce = '';
|
||||||
|
|
||||||
|
private ?User $user = null;
|
||||||
|
|
||||||
|
private ?int $userId = null;
|
||||||
|
|
||||||
|
private string $code = '';
|
||||||
|
|
||||||
|
private string $accessToken = '';
|
||||||
|
|
||||||
private DateTime $created;
|
private DateTime $created;
|
||||||
|
|
||||||
private DateTime $expires;
|
private DateTime $expires;
|
||||||
|
|
||||||
public function setSession(OAuthSession $session): void
|
public function setScopeArray(array $scope): void
|
||||||
{
|
{
|
||||||
$this->session = $session;
|
$this->scope = array_intersect($scope, self::$possibleScopeValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setSessionId(int $sessionId): void
|
public function setScope(string $scope): void
|
||||||
{
|
{
|
||||||
$this->sessionId = $sessionId;
|
$this->setScopeArray(explode(' ', $scope));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setNonce(string $nonce): void
|
||||||
|
{
|
||||||
|
$this->nonce = $nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUser(User $user): void
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUserId(int $userId): void
|
||||||
|
{
|
||||||
|
$this->userId = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCode(string $code): void
|
||||||
|
{
|
||||||
|
$this->code = $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAccessToken(string $accessToken): void
|
||||||
|
{
|
||||||
|
$this->accessToken = $accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCreatedDate(DateTime $created): void
|
public function setCreatedDate(DateTime $created): void
|
||||||
@ -49,14 +84,39 @@ class OAuthToken extends Model
|
|||||||
$this->expires = new DateTime($expires);
|
$this->expires = new DateTime($expires);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSession(): ?OAuthSession
|
public function getScope(): string
|
||||||
{
|
{
|
||||||
return $this->session;
|
return implode(' ', $this->scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSessionId(): ?int
|
public function getScopeArray(): array
|
||||||
{
|
{
|
||||||
return $this->sessionId;
|
return $this->scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNonce(): string
|
||||||
|
{
|
||||||
|
return $this->nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): ?User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserId(): ?int
|
||||||
|
{
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCode(): string
|
||||||
|
{
|
||||||
|
return $this->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccessToken(): string
|
||||||
|
{
|
||||||
|
return $this->accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCreatedDate(): DateTime
|
public function getCreatedDate(): DateTime
|
||||||
|
@ -7,28 +7,19 @@ class Transaction extends Model
|
|||||||
{
|
{
|
||||||
protected static string $table = 'transactions';
|
protected static string $table = 'transactions';
|
||||||
|
|
||||||
protected static array $fields = ['community_id', 'event_id', 'currency_id', 'payer_user_id', 'payee_user_id', 'description', 'sum', 'time'];
|
protected static array $fields = ['community_id', 'currency_id', 'payer_user_id', 'payee_user_id', 'description', 'sum', 'time'];
|
||||||
|
|
||||||
protected static array $relations = [
|
protected static array $relations = [
|
||||||
'community' => Community::class,
|
'community' => Community::class,
|
||||||
'event' => Event::class,
|
|
||||||
'currency' => Currency::class,
|
'currency' => Currency::class,
|
||||||
'payer_user' => User::class,
|
'payer_user' => User::class,
|
||||||
'payee_user' => User::class
|
'payee_user' => User::class
|
||||||
];
|
];
|
||||||
|
|
||||||
protected static array $multiRelations = [
|
|
||||||
'payees' => [TransactionPayee::class, 'transaction']
|
|
||||||
];
|
|
||||||
|
|
||||||
private ?Community $community = null;
|
private ?Community $community = null;
|
||||||
|
|
||||||
private int $communityId;
|
private int $communityId;
|
||||||
|
|
||||||
private ?Event $event = null;
|
|
||||||
|
|
||||||
private ?int $eventId = null;
|
|
||||||
|
|
||||||
private ?Currency $currency = null;
|
private ?Currency $currency = null;
|
||||||
|
|
||||||
private int $currencyId;
|
private int $currencyId;
|
||||||
@ -41,8 +32,6 @@ class Transaction extends Model
|
|||||||
|
|
||||||
private ?int $payeeUserId = null;
|
private ?int $payeeUserId = null;
|
||||||
|
|
||||||
private ?array $payees = null;
|
|
||||||
|
|
||||||
private string $description = '';
|
private string $description = '';
|
||||||
|
|
||||||
private float $sum = 0.0;
|
private float $sum = 0.0;
|
||||||
@ -59,16 +48,6 @@ class Transaction extends Model
|
|||||||
$this->communityId = $communityId;
|
$this->communityId = $communityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setEvent(?Event $event): void
|
|
||||||
{
|
|
||||||
$this->event = $event;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setEventId(?int $eventId): void
|
|
||||||
{
|
|
||||||
$this->eventId = $eventId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCurrency(Currency $currency): void
|
public function setCurrency(Currency $currency): void
|
||||||
{
|
{
|
||||||
$this->currency = $currency;
|
$this->currency = $currency;
|
||||||
@ -99,11 +78,6 @@ class Transaction extends Model
|
|||||||
$this->payeeUserId = $payeeUserId;
|
$this->payeeUserId = $payeeUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPayees(array $payees): void
|
|
||||||
{
|
|
||||||
$this->payees = $payees;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDescription(string $description): void
|
public function setDescription(string $description): void
|
||||||
{
|
{
|
||||||
$this->description = $description;
|
$this->description = $description;
|
||||||
@ -134,16 +108,6 @@ class Transaction extends Model
|
|||||||
return $this->communityId;
|
return $this->communityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEvent(): ?Event
|
|
||||||
{
|
|
||||||
return $this->event;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEventId(): ?int
|
|
||||||
{
|
|
||||||
return $this->eventId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCurrency(): ?Currency
|
public function getCurrency(): ?Currency
|
||||||
{
|
{
|
||||||
return $this->currency;
|
return $this->currency;
|
||||||
@ -174,11 +138,6 @@ class Transaction extends Model
|
|||||||
return $this->payeeUserId;
|
return $this->payeeUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPayees(): ?array
|
|
||||||
{
|
|
||||||
return $this->payees;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDescription(): string
|
public function getDescription(): string
|
||||||
{
|
{
|
||||||
return $this->description;
|
return $this->description;
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
<?php namespace RVR\PersistentData\Model;
|
|
||||||
|
|
||||||
use SokoWeb\PersistentData\Model\Model;
|
|
||||||
|
|
||||||
class TransactionPayee extends Model
|
|
||||||
{
|
|
||||||
protected static string $table = 'transaction_payees';
|
|
||||||
|
|
||||||
protected static array $fields = ['transaction_id', 'user_id'];
|
|
||||||
|
|
||||||
protected static array $relations = ['transaction' => Transaction::class, 'user' => User::class];
|
|
||||||
|
|
||||||
private ?Transaction $transaction = null;
|
|
||||||
|
|
||||||
private ?int $transactionId = null;
|
|
||||||
|
|
||||||
private ?User $user = null;
|
|
||||||
|
|
||||||
private ?int $userId = null;
|
|
||||||
|
|
||||||
public function setTransaction(Transaction $transaction): void
|
|
||||||
{
|
|
||||||
$this->transaction = $transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTransactionId(int $transactionId): void
|
|
||||||
{
|
|
||||||
$this->transactionId = $transactionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUser(User $user): void
|
|
||||||
{
|
|
||||||
$this->user = $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUserId(int $userId): void
|
|
||||||
{
|
|
||||||
$this->userId = $userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTransaction(): ?Transaction
|
|
||||||
{
|
|
||||||
return $this->transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTransactionId(): ?int
|
|
||||||
{
|
|
||||||
return $this->transactionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUser(): ?User
|
|
||||||
{
|
|
||||||
return $this->user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserId(): ?int
|
|
||||||
{
|
|
||||||
return $this->userId;
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,16 +15,12 @@ class CommunityMemberRepository
|
|||||||
|
|
||||||
public function getAllByCommunity(Community $community, bool $useRelations = false, array $withRelations = []): Generator
|
public function getAllByCommunity(Community $community, bool $useRelations = false, array $withRelations = []): Generator
|
||||||
{
|
{
|
||||||
$select = $this->selectAllByCommunity($community);
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('community_id', '=', $community->getId());
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, CommunityMember::class, $useRelations, $withRelations);
|
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, CommunityMember::class, $useRelations, $withRelations);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function countAllByCommunity(Community $community): int
|
|
||||||
{
|
|
||||||
return $this->selectAllByCommunity($community)->count();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllByUser(User $user, bool $useRelations = false, array $withRelations = []): Generator
|
public function getAllByUser(User $user, bool $useRelations = false, array $withRelations = []): Generator
|
||||||
{
|
{
|
||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
@ -41,11 +37,4 @@ class CommunityMemberRepository
|
|||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, CommunityMember::class);
|
return \Container::$persistentDataManager->selectFromDb($select, CommunityMember::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function selectAllByCommunity(Community $community): Select
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection, CommunityMember::getTable());
|
|
||||||
$select->where('community_id', '=', $community->getId());
|
|
||||||
return $select;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,4 @@ class CommunityRepository
|
|||||||
{
|
{
|
||||||
return \Container::$persistentDataManager->selectFromDbById($id, Community::class);
|
return \Container::$persistentDataManager->selectFromDbById($id, Community::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBySlug(string $slug): ?Community
|
|
||||||
{
|
|
||||||
return \Container::$persistentDataManager->selectFromDbBySlug($slug, Community::class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
<?php namespace RVR\Repository;
|
|
||||||
|
|
||||||
use Container;
|
|
||||||
use DateTime;
|
|
||||||
use DateInterval;
|
|
||||||
use Generator;
|
|
||||||
use RVR\PersistentData\Model\Community;
|
|
||||||
use RVR\PersistentData\Model\Event;
|
|
||||||
use RVR\PersistentData\Model\User;
|
|
||||||
use SokoWeb\Database\Query\Select;
|
|
||||||
|
|
||||||
class EventRepository
|
|
||||||
{
|
|
||||||
public function getById(int $id): ?Event
|
|
||||||
{
|
|
||||||
return Container::$persistentDataManager->selectFromDbById($id, Event::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBySlug(string $slug): ?Event
|
|
||||||
{
|
|
||||||
return \Container::$persistentDataManager->selectFromDbBySlug($slug, Event::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllByCommunity(Community $community, bool $useRelations = false, array $withRelations = []): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(Container::$dbConnection, Event::getTable());
|
|
||||||
$this->selectAllByCommunity($select, $community);
|
|
||||||
|
|
||||||
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Event::class, $useRelations, $withRelations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function countAllByCommunity(Community $community): int
|
|
||||||
{
|
|
||||||
$select = new Select(Container::$dbConnection, Event::getTable());
|
|
||||||
$this->selectAllByCommunity($select, $community);
|
|
||||||
|
|
||||||
return $select->count();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUpcomingAndRecentByCommunity(Community $community, DateTime $from, int $days, int $limit, bool $useRelations = false, array $withRelations = []): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(Container::$dbConnection, Event::getTable());
|
|
||||||
$this->selectAllByCommunity($select, $community);
|
|
||||||
$this->selectUpcomingAndRecent($select, $from, $days);
|
|
||||||
$select->orderBy('start', 'DESC');
|
|
||||||
$select->limit($limit, 0);
|
|
||||||
|
|
||||||
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Event::class, $useRelations, $withRelations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUpcomingAndRecentByUser(User $user, DateTime $from, int $days, int $limit, bool $useRelations = false, array $withRelations = []): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(Container::$dbConnection, Event::getTable());
|
|
||||||
$this->selectAllByUser($select, $user);
|
|
||||||
$this->selectUpcomingAndRecent($select, $from, $days);
|
|
||||||
$select->orderBy('start', 'DESC');
|
|
||||||
$select->limit($limit, 0);
|
|
||||||
|
|
||||||
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Event::class, $useRelations, $withRelations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCurrentByUser(User $user, DateTime $from, int $days, bool $useRelations = false, array $withRelations = []): ?Event
|
|
||||||
{
|
|
||||||
$select = new Select(Container::$dbConnection, Event::getTable());
|
|
||||||
$this->selectAllByUser($select, $user);
|
|
||||||
$this->selectUpcomingAndRecent($select, $from, $days);
|
|
||||||
$select->orderBy('start', 'DESC');
|
|
||||||
$select->limit(1, 0);
|
|
||||||
|
|
||||||
return Container::$persistentDataManager->selectFromDb($select, Event::class, $useRelations, $withRelations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPagedByCommunity(Community $community, int $page, int $itemsPerPage, bool $useRelations = false, array $withRelations = []): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(Container::$dbConnection, Event::getTable());
|
|
||||||
$this->selectAllByCommunity($select, $community);
|
|
||||||
$select->orderBy('start', 'DESC');
|
|
||||||
$select->paginate($page, $itemsPerPage);
|
|
||||||
|
|
||||||
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Event::class, $useRelations, $withRelations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function searchByTitle(Community $community, string $title): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(Container::$dbConnection, Event::getTable());
|
|
||||||
$this->selectAllByCommunity($select, $community);
|
|
||||||
$select->where('title', 'LIKE', '%' . $title . '%');
|
|
||||||
$select->orderBy('start', 'DESC');
|
|
||||||
$select->limit(10);
|
|
||||||
|
|
||||||
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Event::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function selectAllByCommunity(Select $select, Community $community): void
|
|
||||||
{
|
|
||||||
$select->where('community_id', '=', $community->getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private function selectAllByUser(Select $select, User $user): void
|
|
||||||
{
|
|
||||||
$select->innerJoin('communities', ['communities', 'id'], '=', ['events', 'community_id']);
|
|
||||||
$select->innerJoin('community_members', ['communities', 'id'], '=', ['community_members', 'community_id']);
|
|
||||||
$select->where(['community_members', 'user_id'], '=', $user->getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private function selectUpcomingAndRecent(Select $select, DateTime $from, int $days): void
|
|
||||||
{
|
|
||||||
$select->where(function (Select $select) use ($from, $days) {
|
|
||||||
$select->where(function (Select $select) use ($from, $days) {
|
|
||||||
$select->where('start', '<', (clone $from)->add(DateInterval::createFromDateString("$days days"))->format('Y-m-d H:i:s'));
|
|
||||||
$select->where('end', '>', $from->format('Y-m-d H:i:s'));
|
|
||||||
});
|
|
||||||
$select->orWhere(function (Select $select) use ($from, $days) {
|
|
||||||
$select->where('end', '>', (clone $from)->sub(DateInterval::createFromDateString("$days days"))->format('Y-m-d H:i:s'));
|
|
||||||
$select->where('start', '<', $from->format('Y-m-d H:i:s'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
<?php namespace RVR\Repository;
|
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use Generator;
|
|
||||||
use SokoWeb\Database\Query\Select;
|
|
||||||
use RVR\PersistentData\Model\OAuthSession;
|
|
||||||
|
|
||||||
class OAuthSessionRepository
|
|
||||||
{
|
|
||||||
public function getById(int $id): ?OAuthSession
|
|
||||||
{
|
|
||||||
return \Container::$persistentDataManager->selectFromDbById($id, OAuthSession::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByCode(string $code): ?OAuthSession
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('code', '=', $code);
|
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, OAuthSession::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllExpired(): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('expires', '<', (new DateTime())->format('Y-m-d H:i:s'));
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, OAuthSession::class);
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,6 @@ use DateTime;
|
|||||||
use Generator;
|
use Generator;
|
||||||
use SokoWeb\Database\Query\Select;
|
use SokoWeb\Database\Query\Select;
|
||||||
use RVR\PersistentData\Model\OAuthToken;
|
use RVR\PersistentData\Model\OAuthToken;
|
||||||
use RVR\PersistentData\Model\OAuthSession;
|
|
||||||
|
|
||||||
class OAuthTokenRepository
|
class OAuthTokenRepository
|
||||||
{
|
{
|
||||||
@ -13,16 +12,20 @@ class OAuthTokenRepository
|
|||||||
return \Container::$persistentDataManager->selectFromDbById($id, OAuthToken::class);
|
return \Container::$persistentDataManager->selectFromDbById($id, OAuthToken::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllBySession(OAuthSession $session, bool $useRelations = false, array $withRelations = []): Generator
|
public function getByCode(string $code): ?OAuthToken
|
||||||
{
|
{
|
||||||
$select = $this->selectAllBySession($session);
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('code', '=', $code);
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, OAuthToken::class, $useRelations, $withRelations);
|
return \Container::$persistentDataManager->selectFromDb($select, OAuthToken::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function countAllBySession(OAuthSession $session): int
|
public function getByAccessToken(string $accessToken): ?OAuthToken
|
||||||
{
|
{
|
||||||
return $this->selectAllBySession($session)->count();
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('access_token', '=', $accessToken);
|
||||||
|
|
||||||
|
return \Container::$persistentDataManager->selectFromDb($select, OAuthToken::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllExpired(): Generator
|
public function getAllExpired(): Generator
|
||||||
@ -32,11 +35,4 @@ class OAuthTokenRepository
|
|||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, OAuthToken::class);
|
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, OAuthToken::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function selectAllBySession(OAuthSession $session): Select
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection, OAuthToken::getTable());
|
|
||||||
$select->where('session_id', '=', $session->getId());
|
|
||||||
return $select;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
<?php namespace RVR\Repository;
|
|
||||||
|
|
||||||
use Container;
|
|
||||||
use Generator;
|
|
||||||
use RVR\PersistentData\Model\Transaction;
|
|
||||||
use RVR\PersistentData\Model\TransactionPayee;
|
|
||||||
use SokoWeb\Database\Query\Select;
|
|
||||||
|
|
||||||
class TransactionPayeeRepository
|
|
||||||
{
|
|
||||||
public function getById(int $id): ?TransactionPayee
|
|
||||||
{
|
|
||||||
return Container::$persistentDataManager->selectFromDbById($id, TransactionPayee::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllByTransaction(Transaction $transaction, bool $useRelations = false, array $withRelations = []): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(Container::$dbConnection);
|
|
||||||
$select->where('transaction_id', '=', $transaction->getId());
|
|
||||||
|
|
||||||
yield from Container::$persistentDataManager->selectMultipleFromDb($select, TransactionPayee::class, $useRelations, $withRelations);
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,6 @@ use Container;
|
|||||||
use Generator;
|
use Generator;
|
||||||
use RVR\PersistentData\Model\Community;
|
use RVR\PersistentData\Model\Community;
|
||||||
use RVR\PersistentData\Model\Currency;
|
use RVR\PersistentData\Model\Currency;
|
||||||
use RVR\PersistentData\Model\Event;
|
|
||||||
use RVR\PersistentData\Model\Transaction;
|
use RVR\PersistentData\Model\Transaction;
|
||||||
use RVR\PersistentData\Model\User;
|
use RVR\PersistentData\Model\User;
|
||||||
use SokoWeb\Database\Query\Select;
|
use SokoWeb\Database\Query\Select;
|
||||||
@ -23,23 +22,11 @@ class TransactionRepository
|
|||||||
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations);
|
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllByEvent(Event $event, bool $useRelations = false, array $withRelations = []): Generator
|
|
||||||
{
|
|
||||||
$select = $this->selectAllByEvent($event);
|
|
||||||
|
|
||||||
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function countAllByCommunity(Community $community): int
|
public function countAllByCommunity(Community $community): int
|
||||||
{
|
{
|
||||||
return $this->selectAllByCommunity($community)->count();
|
return $this->selectAllByCommunity($community)->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function countAllByEvent(Event $event): int
|
|
||||||
{
|
|
||||||
return $this->selectAllByEvent($event)->count();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isAnyForUser(User $user): bool
|
public function isAnyForUser(User $user): bool
|
||||||
{
|
{
|
||||||
$select = new Select(Container::$dbConnection, Transaction::getTable());
|
$select = new Select(Container::$dbConnection, Transaction::getTable());
|
||||||
@ -66,20 +53,12 @@ class TransactionRepository
|
|||||||
return $select->count() > 0;
|
return $select->count() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPagedByCommunity(Community $community, int $page, int $itemsPerPage, bool $useRelations = false, array $withRelations = []): Generator
|
public function getPagedByCommunity(Community $community, int $start, int $limit, bool $useRelations = false, array $withRelations = []): Generator
|
||||||
{
|
{
|
||||||
$select = $this->selectAllByCommunity($community);
|
$select = new Select(Container::$dbConnection);
|
||||||
|
$select->where('community_id', '=', $community->getId());
|
||||||
$select->orderBy('time', 'DESC');
|
$select->orderBy('time', 'DESC');
|
||||||
$select->paginate($page, $itemsPerPage);
|
$select->limit($limit, $start);
|
||||||
|
|
||||||
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPagedByEvent(Event $event, int $page, int $itemsPerPage, bool $useRelations = false, array $withRelations = []): Generator
|
|
||||||
{
|
|
||||||
$select = $this->selectAllByEvent($event);
|
|
||||||
$select->orderBy('time', 'DESC');
|
|
||||||
$select->paginate($page, $itemsPerPage);
|
|
||||||
|
|
||||||
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations);
|
yield from Container::$persistentDataManager->selectMultipleFromDb($select, Transaction::class, $useRelations, $withRelations);
|
||||||
}
|
}
|
||||||
@ -90,11 +69,4 @@ class TransactionRepository
|
|||||||
$select->where('community_id', '=', $community->getId());
|
$select->where('community_id', '=', $community->getId());
|
||||||
return $select;
|
return $select;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function selectAllByEvent(Event $event)
|
|
||||||
{
|
|
||||||
$select = new Select(Container::$dbConnection, Transaction::getTable());
|
|
||||||
$select->where('event_id', '=', $event->getId());
|
|
||||||
return $select;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<p class="justify small">Please confirm your identity with your password or with Google to modify your account.</p>
|
<p class="justify small">Please confirm your identity with your password or with Google to modify your account.</p>
|
||||||
<div class="inputWithButton">
|
<div class="inputWithButton">
|
||||||
<input type="password" class="text" name="password" autocomplete="current-password" required minlength="6" autofocus><!--
|
<input type="password" class="text" name="password" autocomplete="current-password" required minlength="6" autofocus><!--
|
||||||
--><button id="authenticateWithGoogleButton" class="yellow" type="button"><i class="fa-brands fa-google"></i></button>
|
--><button id="authenticateWithGoogleButton" class="yellow" type="button">Google</button>
|
||||||
</div>
|
</div>
|
||||||
<?php elseif ($user['password'] !== null): ?>
|
<?php elseif ($user['password'] !== null): ?>
|
||||||
<p class="justify small">Please confirm your identity with your password to modify your account.</p>
|
<p class="justify small">Please confirm your identity with your password to modify your account.</p>
|
||||||
@ -19,7 +19,7 @@
|
|||||||
<p class="justify small">Please confirm your identity with Google to modify your account.</p>
|
<p class="justify small">Please confirm your identity with Google to modify your account.</p>
|
||||||
<div class="inputWithButton">
|
<div class="inputWithButton">
|
||||||
<input type="text" class="text" name="password" placeholder="Authenticate with Google..." disabled><!--
|
<input type="text" class="text" name="password" placeholder="Authenticate with Google..." disabled><!--
|
||||||
--><button id="authenticateWithGoogleButton" class="yellow" type="button"><i class="fa-brands fa-google"></i></button>
|
--><button id="authenticateWithGoogleButton" class="yellow" type="button">Google</button>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<hr>
|
<hr>
|
||||||
@ -42,19 +42,7 @@
|
|||||||
<input type="text" class="text big fullWidth" name="id_number" value="<?= $user['id_number'] ?>">
|
<input type="text" class="text big fullWidth" name="id_number" value="<?= $user['id_number'] ?>">
|
||||||
<p id="accountFormError" class="formError justify marginTop"></p>
|
<p id="accountFormError" class="formError justify marginTop"></p>
|
||||||
<div class="right marginTop">
|
<div class="right marginTop">
|
||||||
<button type="submit" name="submit_button" disabled><i class="fa-regular fa-floppy-disk"></i> Save</button>
|
<button type="submit" name="submit_button" disabled>Save</button>
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="center">
|
|
||||||
<?php if ($user['google_sub'] === null): ?>
|
|
||||||
<a class="button yellow" href="<?= Container::$routeCollection->getRoute('account.googleConnect')->generateLink() ?>" title="Connect with Google"><i class="fa-solid fa-link"></i> Connect with Google</a>
|
|
||||||
<?php else: ?>
|
|
||||||
<?php if ($user['password'] !== null): ?>
|
|
||||||
<a class="button yellow" href="<?= Container::$routeCollection->getRoute('account.googleDisconnect')->generateLink() ?>" title="Disconnect from Google"><i class="fa-solid fa-link-slash"></i> Disconnect from Google</a>
|
|
||||||
<?php else: ?>
|
|
||||||
<p class="bold small">Your account does not have a password. Please set a password if you want to disconnect your account from Google.</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
@extends(templates/layout_normal)
|
|
||||||
|
|
||||||
@section(main)
|
|
||||||
<h2>Connect with Google</h2>
|
|
||||||
<div class="box compactBox">
|
|
||||||
<?php if (!$success): ?>
|
|
||||||
<p class="error justify"><?= $error ?></p>
|
|
||||||
<?php else: ?>
|
|
||||||
<form id="connectGoogleForm" action="<?= Container::$routeCollection->getRoute('account.googleConnect-action')->generateLink() ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('account')->generateLink() ?>">
|
|
||||||
<p class="justify marginBottom">Your account will be connected with the following Google account: <b><?= $googleAccount ?></b></p>
|
|
||||||
<input type="email" style="display: none;" name="email" autocomplete="username" value="<?= $userEmail ?>">
|
|
||||||
<p class="formLabel marginTop">Password</p>
|
|
||||||
<input type="password" class="text big fullWidth" name="password" autocomplete="current-password" required minlength="6" autofocus>
|
|
||||||
<p class="formError justify marginTop"></p>
|
|
||||||
<div class="right marginTop">
|
|
||||||
<button class="marginRight" type="submit" name="submit"><i class="fa-solid fa-link"></i> Connect</button><!--
|
|
||||||
--><a class="button gray" href="<?= Container::$routeCollection->getRoute('account')->generateLink() ?>" title="Back to account">Cancel</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
@endsection
|
|
@ -1,18 +0,0 @@
|
|||||||
@extends(templates/layout_normal)
|
|
||||||
|
|
||||||
@section(main)
|
|
||||||
<h2>Disconnect from Google</h2>
|
|
||||||
<div class="box compactBox">
|
|
||||||
<form id="connectGoogleForm" action="<?= Container::$routeCollection->getRoute('account.googleDisconnect-action')->generateLink() ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('account')->generateLink() ?>">
|
|
||||||
<p class="justify marginBottom">Your account will be disconnected from the currently set Google account.</p>
|
|
||||||
<input type="email" style="display: none;" name="email" autocomplete="username" value="<?= $userEmail ?>">
|
|
||||||
<p class="formLabel marginTop">Password</p>
|
|
||||||
<input type="password" class="text big fullWidth" name="password" autocomplete="current-password" required minlength="6" autofocus>
|
|
||||||
<p class="formError justify marginTop"></p>
|
|
||||||
<div class="right marginTop">
|
|
||||||
<button class="red marginRight" type="submit" name="submit"><i class="fa-solid fa-link-slash"></i> Disconnect</button><!--
|
|
||||||
--><a class="button gray" href="<?= Container::$routeCollection->getRoute('account')->generateLink() ?>" title="Back to account">Cancel</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
@endsection
|
|
@ -4,26 +4,22 @@
|
|||||||
<h2>
|
<h2>
|
||||||
<?= $community->getName() ?>
|
<?= $community->getName() ?>
|
||||||
<?php if ($editPermission): ?>
|
<?php if ($editPermission): ?>
|
||||||
<span class="small">[<a href="<?= Container::$routeCollection->getRoute('community.edit')->generateLink(['communitySlug' => $community->getSlug()]) ?>">edit</a>]</span>
|
<span class="small">[<a href="<?= Container::$routeCollection->getRoute('community.edit')->generateLink(['communityId' => $community->getId()]) ?>">edit</a>]</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.settings')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Settings</a></p>
|
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.settings')->generateLink(['communityId' => $community->getId()]) ?>">Settings</a></p>
|
||||||
|
|
||||||
<div class="gridContainer marginTop">
|
<div class="gridContainer marginTop">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="marginBottom">Upcoming and recent events</h3>
|
<h3 class="marginBottom">Upcoming events</h3>
|
||||||
<?php if (count($upcomingAndRecentEvents) > 0): ?>
|
<?php if (count($upcomingEvents) > 0): ?>
|
||||||
<?php foreach ($upcomingAndRecentEvents as $event): ?>
|
<?php foreach ($upcomingEvents as $event): ?>
|
||||||
<p>
|
<!-- todo -->
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) ?>"><?= $event->getTitle() ?></a>
|
|
||||||
<span class="small"><?= $event->getStartDate()->format('Y-m-d') ?> – <?= $event->getEndDate()->format('Y-m-d') ?></span>
|
|
||||||
</p>
|
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p>There is no event to show.</p>
|
<p>There is no upcoming event.</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug()]) ?>">All events</a> | <a href="<?= Container::$routeCollection->getRoute('community.events.new')->generateLink(['communitySlug' => $community->getSlug()]) ?>">New event</a></p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<?php
|
<?php
|
||||||
@ -31,7 +27,7 @@
|
|||||||
$mainCurrencyRoundDigits = $community->getMainCurrency()->getRoundDigits();
|
$mainCurrencyRoundDigits = $community->getMainCurrency()->getRoundDigits();
|
||||||
?>
|
?>
|
||||||
<h3 class="marginBottom">Finances</h3>
|
<h3 class="marginBottom">Finances</h3>
|
||||||
<p><a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Transactions</a> | <a href="<?= Container::$routeCollection->getRoute('community.transactions.new')->generateLink(['communitySlug' => $community->getSlug()]) ?>">New transaction</a></p>
|
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId()]) ?>">Transactions</a>
|
||||||
<table class="fullWidth marginTop">
|
<table class="fullWidth marginTop">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="bold">You owe</td>
|
<td class="bold">You owe</td>
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
@section(main)
|
@section(main)
|
||||||
<h2>
|
<h2>
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a> »
|
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> »
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community.settings')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Settings</a> »
|
<a href="<?= Container::$routeCollection->getRoute('community.settings')->generateLink(['communityId' => $community->getId()]) ?>">Settings</a> »
|
||||||
Edit currencies
|
Edit currencies
|
||||||
</h2>
|
</h2>
|
||||||
<div class="box compactBox">
|
<div class="box compactBox">
|
||||||
@ -18,31 +18,31 @@
|
|||||||
<?php foreach ($currencies as $currency): ?>
|
<?php foreach ($currencies as $currency): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<form id="editCurrency_<?= $currency->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.currencies.edit-action')->generateLink(['communitySlug' => $community->getSlug(), 'currency_id' => $currency->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="code,round_digits"></form>
|
<form id="editCurrency_<?= $currency->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.currencies.edit-action')->generateLink(['communityId' => $community->getId(), 'currency_id' => $currency->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="code,round_digits"></form>
|
||||||
<form id="deleteCurrency_<?= $currency->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.currencies.delete-action')->generateLink(['communitySlug' => $community->getSlug(), 'currency_id' => $currency->getId()]) ?>" method="post" data-reload-on-success="true"></form>
|
<form id="deleteCurrency_<?= $currency->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.currencies.delete-action')->generateLink(['communityId' => $community->getId(), 'currency_id' => $currency->getId()]) ?>" method="post" data-reload-on-success="true"></form>
|
||||||
<input type="text" form="editCurrency_<?= $currency->getId() ?>" class="text fullWidth" name="code" value="<?= $currency->getCode() ?>" maxlength="3" required>
|
<input type="text" form="editCurrency_<?= $currency->getId() ?>" class="text fullWidth" name="code" value="<?= $currency->getCode() ?>" maxlength="3" required>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="number" form="editCurrency_<?= $currency->getId() ?>" class="text fullWidth" name="round_digits" value="<?= $currency->getRoundDigits() ?>" min="0" max="9" required>
|
<input type="number" form="editCurrency_<?= $currency->getId() ?>" class="text fullWidth" name="round_digits" value="<?= $currency->getRoundDigits() ?>" min="0" max="9" required>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right; font-size: 0;">
|
<td style="text-align: right; font-size: 0;">
|
||||||
<button type="submit" form="editCurrency_<?= $currency->getId() ?>" name="submit_button" class="small" disabled><i class="fa-regular fa-floppy-disk"></i></button>
|
<button type="submit" form="editCurrency_<?= $currency->getId() ?>" name="submit_button" class="small" disabled>Save</button>
|
||||||
<?php if ($currency->getId() !== $community->getMainCurrencyId()): ?>
|
<?php if ($currency->getId() !== $community->getMainCurrencyId()): ?>
|
||||||
<button type="submit" form="deleteCurrency_<?= $currency->getId() ?>" name="submit_button" data-confirmation="Are you sure you want to delete this currency?" data-confirmation-button='<i class="fa-regular fa-trash-can"></i> Delete' class="small red marginLeft"><i class="fa-regular fa-trash-can"></i></button>
|
<button type="submit" form="deleteCurrency_<?= $currency->getId() ?>" name="submit_button" data-confirmation="Are you sure you want to delete this currency?" class="small red marginLeft">Delete</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<form id="newCurrency" action="<?= Container::$routeCollection->getRoute('community.currencies.new-action')->generateLink(['communitySlug' => $community->getSlug()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="code,round_digits"></form>
|
<form id="newCurrency" action="<?= Container::$routeCollection->getRoute('community.currencies.new-action')->generateLink(['communityId' => $community->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="code,round_digits"></form>
|
||||||
<input type="text" form="newCurrency" class="text fullWidth" name="code" maxlength="3" required>
|
<input type="text" form="newCurrency" class="text fullWidth" name="code" maxlength="3" required>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="number" form="newCurrency" class="text fullWidth" name="round_digits" min="0" max="9" required>
|
<input type="number" form="newCurrency" class="text fullWidth" name="round_digits" min="0" max="9" required>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right;">
|
<td style="text-align: right;">
|
||||||
<button type="submit" form="newCurrency" name="submit_button" class="small" disabled><i class="fa-regular fa-plus"></i></button>
|
<button type="submit" form="newCurrency" name="submit_button" class="small" disabled>Add</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
@section(main)
|
@section(main)
|
||||||
<h2>
|
<h2>
|
||||||
<?php if (isset($community)): ?>
|
<?php if (isset($community)): ?>
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a> »
|
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> »
|
||||||
Edit
|
Edit
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
New community
|
New community
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<div class="box compactBox">
|
<div class="box compactBox">
|
||||||
<?php
|
<?php
|
||||||
$formAction = isset($community) ?
|
$formAction = isset($community) ?
|
||||||
Container::$routeCollection->getRoute('community.edit-action')->generateLink(['communitySlug' => $community->getSlug()]) :
|
Container::$routeCollection->getRoute('community.edit-action')->generateLink(['communityId' => $community->getId()]) :
|
||||||
Container::$routeCollection->getRoute('community.new-action')->generateLink();
|
Container::$routeCollection->getRoute('community.new-action')->generateLink();
|
||||||
?>
|
?>
|
||||||
<form id="communityForm" action="<?= $formAction ?>" method="post" data-redirect-on-success="true">
|
<form id="communityForm" action="<?= $formAction ?>" method="post" data-redirect-on-success="true">
|
||||||
@ -25,15 +25,9 @@
|
|||||||
<input type="number" class="text big fullWidth" name="main_currency_round_digits" min="0" max="9" required>
|
<input type="number" class="text big fullWidth" name="main_currency_round_digits" min="0" max="9" required>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<p id="communityFormError" class="formError justify marginTop"></p>
|
<p id="communityFormError" class="formError justify marginTop"></p>
|
||||||
<div class="right marginTop" style="font-size: 0;">
|
<div class="right marginTop">
|
||||||
<button type="submit" name="submit_button"><?= isset($community) ? '<i class="fa-regular fa-floppy-disk"></i> Save' : '<i class="fa-regular fa-plus"></i> Create' ?></button>
|
<button type="submit" name="submit_button"><?= isset($community) ? 'Save' : 'Create' ?></button>
|
||||||
<?php if (isset($community)): ?>
|
|
||||||
<button type="submit" form="deleteCommunity" name="submit_button" data-confirmation="Are you sure you want to delete this community?" class="red marginLeft"><i class="fa-regular fa-trash-can"></i> Delete</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<?php if (isset($community)): ?>
|
|
||||||
<form id="deleteCommunity" action="<?= Container::$routeCollection->getRoute('community.delete-action')->generateLink(['communitySlug' => $community->getSlug()]) ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('home')->generateLink() ?>"></form>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
@css(node_modules/tom-select/dist/css/tom-select.min.css)
|
@css(/static/node_modules/tom-select/dist/css/tom-select.min.css)
|
||||||
@js(node_modules/tom-select/dist/js/tom-select.base.min.js)
|
@js(/static/node_modules/tom-select/dist/js/tom-select.base.min.js)
|
||||||
@js(js/communities/community_members.js)
|
@js(js/communities/community_members.js)
|
||||||
|
|
||||||
@extends(templates/layout_normal)
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
@section(main)
|
@section(main)
|
||||||
<h2>
|
<h2>
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a> »
|
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> »
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community.settings')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Settings</a> »
|
<a href="<?= Container::$routeCollection->getRoute('community.settings')->generateLink(['communityId' => $community->getId()]) ?>">Settings</a> »
|
||||||
Edit members
|
Edit members
|
||||||
</h2>
|
</h2>
|
||||||
<div class="box compactBox">
|
<div class="box compactBox">
|
||||||
@ -23,8 +23,8 @@
|
|||||||
<?php $editable = $member->getUserId() !== Container::$request->user()->getUniqueId(); ?>
|
<?php $editable = $member->getUserId() !== Container::$request->user()->getUniqueId(); ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<form id="editMember_<?= $member->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.members.edit-action')->generateLink(['communitySlug' => $community->getSlug(), 'community_member_id' => $member->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="owner"></form>
|
<form id="editMember_<?= $member->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.members.edit-action')->generateLink(['communityId' => $community->getId(), 'community_member_id' => $member->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="owner"></form>
|
||||||
<form id="deleteMember_<?= $member->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.members.delete-action')->generateLink(['communitySlug' => $community->getSlug(), 'community_member_id' => $member->getId()]) ?>" method="post" data-reload-on-success="true"></form>
|
<form id="deleteMember_<?= $member->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.members.delete-action')->generateLink(['communityId' => $community->getId(), 'community_member_id' => $member->getId()]) ?>" method="post" data-reload-on-success="true"></form>
|
||||||
<?= $member->getUser()->getDisplayName() ?>
|
<?= $member->getUser()->getDisplayName() ?>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: center;">
|
<td style="text-align: center;">
|
||||||
@ -32,22 +32,22 @@
|
|||||||
</td>
|
</td>
|
||||||
<td style="text-align: right;">
|
<td style="text-align: right;">
|
||||||
<?php if ($editable): ?>
|
<?php if ($editable): ?>
|
||||||
<button type="submit" form="editMember_<?= $member->getId() ?>" name="submit_button" class="small marginRight" disabled><i class="fa-regular fa-floppy-disk"></i></button><!--
|
<button type="submit" form="editMember_<?= $member->getId() ?>" name="submit_button" class="small marginRight" disabled>Save</button><!--
|
||||||
--><button type="submit" form="deleteMember_<?= $member->getId() ?>" name="submit_button" data-confirmation="Are you sure you want to delete this member?" data-confirmation-button='<i class="fa-regular fa-trash-can"></i> Delete' class="small red"><i class="fa-regular fa-trash-can"></i></button>
|
--><button type="submit" form="deleteMember_<?= $member->getId() ?>" name="submit_button" data-confirmation="Are you sure you want to delete this member?" class="small red">Delete</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<form id="newMember" action="<?= Container::$routeCollection->getRoute('community.members.new-action')->generateLink(['communitySlug' => $community->getSlug()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="user_id"></form>
|
<form id="newMember" action="<?= Container::$routeCollection->getRoute('community.members.new-action')->generateLink(['communityId' => $community->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="user_id"></form>
|
||||||
<select form="newMember" name="user_id" required></select>
|
<select type="text" form="newMember" name="user_id">
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: center;">
|
<td style="text-align: center;">
|
||||||
<input type="checkbox" form="newMember" name="owner" />
|
<input type="checkbox" form="newMember" name="owner" />
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right;">
|
<td style="text-align: right;">
|
||||||
<button type="submit" form="newMember" name="submit_button" class="small" disabled><i class="fa-regular fa-plus"></i></button>
|
<button type="submit" form="newMember" name="submit_button" class="small" disabled>Add</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
@section(main)
|
@section(main)
|
||||||
<h2>
|
<h2>
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a> »
|
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> »
|
||||||
Settings
|
Settings
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
@ -11,7 +11,7 @@
|
|||||||
<h3 class="marginBottom">
|
<h3 class="marginBottom">
|
||||||
Members
|
Members
|
||||||
<?php if ($editPermission): ?>
|
<?php if ($editPermission): ?>
|
||||||
<span class="small">[<a href="<?= Container::$routeCollection->getRoute('community.members')->generateLink(['communitySlug' => $community->getSlug()]) ?>">edit</a>]</span>
|
<span class="small">[<a href="<?= Container::$routeCollection->getRoute('community.members')->generateLink(['communityId' => $community->getId()]) ?>">edit</a>]</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</h3>
|
</h3>
|
||||||
<?php foreach ($members as $member): ?>
|
<?php foreach ($members as $member): ?>
|
||||||
@ -22,7 +22,7 @@
|
|||||||
<h3 class="marginBottom">
|
<h3 class="marginBottom">
|
||||||
Currencies
|
Currencies
|
||||||
<?php if ($editPermission): ?>
|
<?php if ($editPermission): ?>
|
||||||
<span class="small">[<a href="<?= Container::$routeCollection->getRoute('community.currencies')->generateLink(['communitySlug' => $community->getSlug()]) ?>">edit</a>]</span>
|
<span class="small">[<a href="<?= Container::$routeCollection->getRoute('community.currencies')->generateLink(['communityId' => $community->getId()]) ?>">edit</a>]</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</h3>
|
</h3>
|
||||||
<?php foreach ($currencies as $currency): ?>
|
<?php foreach ($currencies as $currency): ?>
|
||||||
@ -30,7 +30,7 @@
|
|||||||
<?php if ($currency->getId() === $community->getMainCurrencyId()): ?>
|
<?php if ($currency->getId() === $community->getMainCurrencyId()): ?>
|
||||||
<b><?= $currency->getCode() ?></b>
|
<b><?= $currency->getCode() ?></b>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community.currencyExchangeRates')->generateLink(['communitySlug' => $community->getSlug(), 'code' => $currency->getCode()]) ?>"><?= $currency->getCode() ?></a>
|
<a href="<?= Container::$routeCollection->getRoute('community.currencyExchangeRates')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode()]) ?>"><?= $currency->getCode() ?></a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</p>
|
</p>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
@section(main)
|
@section(main)
|
||||||
<h2>
|
<h2>
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a> »
|
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> »
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community.settings')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Settings</a> »
|
<a href="<?= Container::$routeCollection->getRoute('community.settings')->generateLink(['communityId' => $community->getId()]) ?>">Settings</a> »
|
||||||
Exchange rates for <?= $currency->getCode() ?>
|
Exchange rates for <?= $currency->getCode() ?>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="box compactBox">
|
<div class="box compactBox">
|
||||||
@ -24,16 +24,16 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<?php if ($editPermission): ?>
|
<?php if ($editPermission): ?>
|
||||||
<td>
|
<td>
|
||||||
<form id="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.currencyExchangeRates.edit-action')->generateLink(['communitySlug' => $community->getSlug(), 'code' => $currency->getCode(), 'currency_exchange_rate_id' => $currencyExchangeRate->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="exchange_rate,valid_from"></form>
|
<form id="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.currencyExchangeRates.edit-action')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode(), 'currency_exchange_rate_id' => $currencyExchangeRate->getId()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="exchange_rate,valid_from"></form>
|
||||||
<form id="deleteCurrency_<?= $currencyExchangeRate->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.currencyExchangeRates.delete-action')->generateLink(['communitySlug' => $community->getSlug(), 'code' => $currency->getCode(), 'currency_exchange_rate_id' => $currencyExchangeRate->getId()]) ?>" method="post" data-reload-on-success="true"></form>
|
<form id="deleteCurrency_<?= $currencyExchangeRate->getId() ?>" action="<?= Container::$routeCollection->getRoute('community.currencyExchangeRates.delete-action')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode(), 'currency_exchange_rate_id' => $currencyExchangeRate->getId()]) ?>" method="post" data-reload-on-success="true"></form>
|
||||||
<input type="number" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" class="text fullWidth" name="exchange_rate" value="<?= $currencyExchangeRate->getExchangeRate() ?>" min="0" step="0.000000001" required>
|
<input type="number" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" class="text fullWidth" name="exchange_rate" value="<?= $currencyExchangeRate->getExchangeRate() ?>" min="0" step="0.000000001" required>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="datetime-local" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" class="text fullWidth" name="valid_from" value="<?= $currencyExchangeRate->getValidFromDate()->format('Y-m-d\TH:i') ?>" required>
|
<input type="datetime-local" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" class="text fullWidth" name="valid_from" value="<?= $currencyExchangeRate->getValidFromDate()->format('Y-m-d\TH:i') ?>" required>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right;">
|
<td style="text-align: right;">
|
||||||
<button type="submit" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" name="submit_button" class="small marginRight" disabled><i class="fa-regular fa-floppy-disk"></i></button><!--
|
<button type="submit" form="editExchangeRate_<?= $currencyExchangeRate->getId() ?>" name="submit_button" class="small marginRight" disabled>Save</button><!--
|
||||||
--><button type="submit" form="deleteCurrency_<?= $currencyExchangeRate->getId() ?>" name="submit_button" data-confirmation="Are you sure you want to delete this exchange rate?" data-confirmation-button='<i class="fa-regular fa-trash-can"></i> Delete' class="small red"><i class="fa-regular fa-trash-can"></i></button>
|
--><button type="submit" form="deleteCurrency_<?= $currencyExchangeRate->getId() ?>" name="submit_button" data-confirmation="Are you sure you want to delete this exchange rate?" class="small red">Delete</button>
|
||||||
</td>
|
</td>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<td><?= $currencyExchangeRate->getExchangeRate() ?></td>
|
<td><?= $currencyExchangeRate->getExchangeRate() ?></td>
|
||||||
@ -44,14 +44,14 @@
|
|||||||
<?php if ($editPermission): ?>
|
<?php if ($editPermission): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<form id="newExchangeRate" action="<?= Container::$routeCollection->getRoute('community.currencyExchangeRates.new-action')->generateLink(['communitySlug' => $community->getSlug(), 'code' => $currency->getCode()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="exchange_rate,valid_from"></form>
|
<form id="newExchangeRate" action="<?= Container::$routeCollection->getRoute('community.currencyExchangeRates.new-action')->generateLink(['communityId' => $community->getId(), 'code' => $currency->getCode()]) ?>" method="post" data-reload-on-success="true" data-observe-inputs="exchange_rate,valid_from"></form>
|
||||||
<input type="number" form="newExchangeRate" class="text fullWidth" name="exchange_rate" min="0" step="0.000000001" required>
|
<input type="number" form="newExchangeRate" class="text fullWidth" name="exchange_rate" min="0" step="0.000000001" required>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="datetime-local" form="newExchangeRate" class="text fullWidth" name="valid_from" required>
|
<input type="datetime-local" form="newExchangeRate" class="text fullWidth" name="valid_from" required>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align: right;">
|
<td style="text-align: right;">
|
||||||
<button type="submit" form="newExchangeRate" name="submit_button" class="small" disabled><i class="fa-regular fa-plus"></i></button>
|
<button type="submit" form="newExchangeRate" name="submit_button" class="small" disabled>Add</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
@ -1,17 +1,9 @@
|
|||||||
@css(node_modules/tom-select/dist/css/tom-select.min.css)
|
|
||||||
@js(node_modules/tom-select/dist/js/tom-select.base.min.js)
|
|
||||||
@js(js/communities/transaction.js)
|
|
||||||
|
|
||||||
@extends(templates/layout_normal)
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
@section(main)
|
@section(main)
|
||||||
<h2>
|
<h2>
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a> »
|
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> »
|
||||||
<?php if (isset($event)): ?>
|
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId()]) ?>">Transactions</a> »
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Events</a> »
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) ?>"><?= $event->getTitle() ?></a> »
|
|
||||||
<?php endif; ?>
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'event' => isset($event) ? $event->getSlug() : null]) ?>">Transactions</a> »
|
|
||||||
<?php if (isset($transaction)): ?>
|
<?php if (isset($transaction)): ?>
|
||||||
Edit transaction
|
Edit transaction
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
@ -21,38 +13,24 @@
|
|||||||
<div class="box compactBox">
|
<div class="box compactBox">
|
||||||
<?php
|
<?php
|
||||||
$formAction = isset($transaction) ?
|
$formAction = isset($transaction) ?
|
||||||
Container::$routeCollection->getRoute('community.transactions.edit-action')->generateLink(['communitySlug' => $community->getSlug(), 'transactionId' => $transaction->getId()]) :
|
Container::$routeCollection->getRoute('community.transactions.edit-action')->generateLink(['communityId' => $community->getId(), 'transactionId' => $transaction->getId()]) :
|
||||||
Container::$routeCollection->getRoute('community.transactions.new-action')->generateLink(['communitySlug' => $community->getSlug()]);
|
Container::$routeCollection->getRoute('community.transactions.new-action')->generateLink(['communityId' => $community->getId()]);
|
||||||
?>
|
?>
|
||||||
<form id="transactionForm" action="<?= $formAction ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'event' => isset($event) ? $event->getSlug() : null]) ?>">
|
<form id="transactionForm" action="<?= $formAction ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId()]) ?>">
|
||||||
<p class="formLabel">Event</p>
|
<p class="formLabel">Payer</p>
|
||||||
<select name="event_id">
|
|
||||||
<option value="">[none]</option>
|
|
||||||
<?php if (isset($event)): ?>
|
|
||||||
<option value="<?= $event->getId() ?>" selected><?= $event->getTitle() ?></option>
|
|
||||||
<?php endif; ?>
|
|
||||||
</select>
|
|
||||||
<p class="formLabel marginTop">Payer</p>
|
|
||||||
<select class="big fullWidth" name="payer_user_id" required>
|
<select class="big fullWidth" name="payer_user_id" required>
|
||||||
<option value="" hidden></option>
|
<option value="" hidden></option>
|
||||||
<?php foreach ($members as $member): ?>
|
<?php foreach ($members as $member): ?>
|
||||||
<option value="<?= $member->getUser()->getId() ?>"
|
<option value="<?= $member->getUser()->getId() ?>" <?= isset($transaction) && $transaction->getPayerUserId() === $member->getUser()->getId() ? 'selected' : '' ?>><?= $member->getUser()->getDisplayName() ?></option>
|
||||||
<?= isset($transaction) ?
|
|
||||||
($transaction->getPayerUserId() === $member->getUser()->getId() ? 'selected' : '') :
|
|
||||||
(Container::$request->user()->getUniqueId() == $member->getUser()->getId() ? 'selected' : '') ?>>
|
|
||||||
<?= $member->getUser()->getDisplayName() ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
<p class="formLabel marginTop">Payee(s)</p>
|
<p class="formLabel marginTop">Payee</p>
|
||||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); grid-gap: 10px;">
|
<select class="big fullWidth" name="payee_user_id">
|
||||||
|
<option value="">[common]</option>
|
||||||
<?php foreach ($members as $member): ?>
|
<?php foreach ($members as $member): ?>
|
||||||
<div style="text-align: center;">
|
<option value="<?= $member->getUser()->getId() ?>" <?= isset($transaction) && $transaction->getPayeeUserId() === $member->getUser()->getId() ? 'selected' : '' ?>><?= $member->getUser()->getDisplayName() ?></option>
|
||||||
<input id="payee_<?= $member->getUserId() ?>" type="checkbox" name="payee_user_ids[]" value="<?= $member->getUserId() ?>" <?= !isset($transaction) || count($payeeUserIds) === 0 || in_array($member->getUserId(), $payeeUserIds) ? 'checked' : '' ?>><!--
|
|
||||||
--><label for="payee_<?= $member->getUserId() ?>"><?= $member->getUser()->getDisplayName() ?></label>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</select>
|
||||||
<p class="formLabel marginTop">Description</p>
|
<p class="formLabel marginTop">Description</p>
|
||||||
<input type="text" class="text big fullWidth" name="description" value="<?= isset($transaction) ? $transaction->getDescription() : '' ?>" required>
|
<input type="text" class="text big fullWidth" name="description" value="<?= isset($transaction) ? $transaction->getDescription() : '' ?>" required>
|
||||||
<p class="formLabel marginTop">Sum</p>
|
<p class="formLabel marginTop">Sum</p>
|
||||||
@ -68,20 +46,14 @@
|
|||||||
<input type="datetime-local" class="text big fullWidth" name="time" value="<?= isset($transaction) ? $transaction->getTimeDate()->format('Y-m-d\TH:i') : (new DateTime())->format('Y-m-d\TH:i') ?>" required>
|
<input type="datetime-local" class="text big fullWidth" name="time" value="<?= isset($transaction) ? $transaction->getTimeDate()->format('Y-m-d\TH:i') : (new DateTime())->format('Y-m-d\TH:i') ?>" required>
|
||||||
<p class="formError justify marginTop"></p>
|
<p class="formError justify marginTop"></p>
|
||||||
<div class="right marginTop" style="font-size: 0;">
|
<div class="right marginTop" style="font-size: 0;">
|
||||||
<button type="submit" name="submit_button"><?= isset($transaction) ? '<i class="fa-regular fa-floppy-disk"></i> Save' : '<i class="fa-regular fa-plus"></i> Create' ?></button>
|
<button type="submit" name="submit_button"><?= isset($transaction) ? 'Save' : 'Create' ?></button>
|
||||||
<?php if (isset($transaction)): ?>
|
<?php if (isset($transaction)): ?>
|
||||||
<button type="submit" form="deleteTransaction" name="submit_button" data-confirmation="Are you sure you want to delete this transaction?" class="red marginLeft"><i class="fa-regular fa-trash-can"></i> Delete</button>
|
<button type="submit" form="deleteTransaction" name="submit_button" data-confirmation="Are you sure you want to delete this transaction?" class="red marginRight">Delete</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<?php if (isset($transaction)): ?>
|
<?php if (isset($transaction)): ?>
|
||||||
<form id="deleteTransaction" action="<?= Container::$routeCollection->getRoute('community.transactions.delete-action')->generateLink(['communitySlug' => $community->getSlug(), 'transactionId' => $transaction->getId()]) ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug()]) ?>"></form>
|
<form id="deleteTransaction" action="<?= Container::$routeCollection->getRoute('community.transactions.delete-action')->generateLink(['communityId' => $community->getId(), 'transactionId' => $transaction->getId()]) ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId()]) ?>"></form>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section(pageScript)
|
|
||||||
<script>
|
|
||||||
var searchEventUrl = '<?= Container::$routeCollection->getRoute('community.events.search')->generateLink(['communitySlug' => $community->getSlug(), 'q' => 'QUERY']) ?>';
|
|
||||||
</script>
|
|
||||||
@endsection
|
|
||||||
|
@ -2,57 +2,35 @@
|
|||||||
|
|
||||||
@section(main)
|
@section(main)
|
||||||
<h2>
|
<h2>
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a> »
|
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a> »
|
||||||
<?php if (isset($event)): ?>
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Events</a> »
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) ?>"><?= $event->getTitle() ?></a> »
|
|
||||||
<?php endif; ?>
|
|
||||||
Transactions
|
Transactions
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.transactions.new')->generateLink(['communitySlug' => $community->getSlug(), 'event' => isset($event) ? $event->getSlug() : null]) ?>">New transaction</a></p>
|
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.transactions.new')->generateLink(['communityId' => $community->getId()]) ?>">New transaction</a></p>
|
||||||
|
|
||||||
<?php if ($numberOfTransactions > 0): ?>
|
<?php if ($numberOfTransactions > 0): ?>
|
||||||
<?php
|
|
||||||
$paginationRouteId = 'community.transactions';
|
|
||||||
$paginationRouteParams = ['communitySlug' => $community->getSlug()];
|
|
||||||
?>
|
|
||||||
|
|
||||||
<?php if ($pages > 1): ?>
|
<?php if ($pages > 1): ?>
|
||||||
@include(templates/pagination)
|
<p class="paginateContainer marginTop">
|
||||||
|
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => 0]) ?>">«</a>
|
||||||
|
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => max(0, $currentPage - 1)]) ?>">‹</a>
|
||||||
|
<?php for ($i = 0; $i < $pages; $i++): ?>
|
||||||
|
<?php if ($currentPage == $i): ?>
|
||||||
|
<span class="selected"><?= $i + 1 ?></span>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => $i]) ?>"><?= $i + 1 ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endfor; ?>
|
||||||
|
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => min($pages - 1, $currentPage + 1)]) ?>">›</a>
|
||||||
|
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => $pages - 1]) ?>">»</a>
|
||||||
|
</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php foreach ($transactions as $transaction): ?>
|
<?php foreach ($transactions as $transaction): ?>
|
||||||
<a class="block" href="<?= Container::$routeCollection->getRoute('community.transactions.edit')->generateLink(['communitySlug' => $community->getSlug(), 'transactionId' => $transaction->getId()]) ?>">
|
<a class="block" href="<?= Container::$routeCollection->getRoute('community.transactions.edit')->generateLink(['communityId' => $community->getId(), 'transactionId' => $transaction->getId()]) ?>">
|
||||||
<div class="box transaction">
|
<div class="box transaction">
|
||||||
<div>
|
<div>
|
||||||
<?php if ($transaction->getEvent()): ?>
|
|
||||||
<p><span class="label"><?= $transaction->getEvent()->getTitle() ?></span></p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<p style="font-weight: bold;"><?= $transaction->getDescription() ?></p>
|
<p style="font-weight: bold;"><?= $transaction->getDescription() ?></p>
|
||||||
<p class="small">
|
<p class="small"><?= $transaction->getPayerUser()->getDisplayName() ?> ► <?= $transaction->getPayeeUser() ? $transaction->getPayeeUser()->getDisplayName() : '[common]' ?></p>
|
||||||
<?= $transaction->getPayerUser()->getDisplayName() ?>
|
|
||||||
<i class="fa-solid fa-caret-right"></i>
|
|
||||||
<?php foreach ($members as $member): ?>
|
|
||||||
<?php
|
|
||||||
if (count($transaction->getPayees()) > 0) {
|
|
||||||
$found = false;
|
|
||||||
foreach ($transaction->getPayees() as $payee) {
|
|
||||||
if ($member->getUserId() === $payee->getUserId()) {
|
|
||||||
$found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$found = true;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<?php if ($found): ?>
|
|
||||||
<?= $member->getUser()->getDisplayName() ?>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="gray" style="text-decoration: line-through;"><?= $member->getUser()->getDisplayName() ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</p>
|
|
||||||
<p class="small"><?= $transaction->getTimeDate()->format('Y-m-d H:i') ?></p>
|
<p class="small"><?= $transaction->getTimeDate()->format('Y-m-d H:i') ?></p>
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align: right;">
|
<div style="text-align: right;">
|
||||||
@ -66,10 +44,22 @@
|
|||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|
||||||
<?php if ($pages > 1): ?>
|
<?php if ($pages > 1): ?>
|
||||||
@include(templates/pagination)
|
<p class="paginateContainer marginTop">
|
||||||
|
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => 0]) ?>">«</a>
|
||||||
|
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => max(0, $currentPage - 1)]) ?>">‹</a>
|
||||||
|
<?php for ($i = 0; $i < $pages; $i++): ?>
|
||||||
|
<?php if ($currentPage == $i): ?>
|
||||||
|
<span class="selected"><?= $i + 1 ?></span>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => $i]) ?>"><?= $i + 1 ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endfor; ?>
|
||||||
|
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => min($pages - 1, $currentPage + 1)]) ?>">›</a>
|
||||||
|
<a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communityId' => $community->getId(), 'page' => $pages - 1]) ?>">»</a>
|
||||||
|
</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.transactions.new')->generateLink(['communitySlug' => $community->getSlug(), 'event' => isset($event) ? $event->getSlug() : null]) ?>">New transaction</a></p>
|
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.transactions.new')->generateLink(['communityId' => $community->getId()]) ?>">New transaction</a></p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<p>There are no transactions yet.</p>
|
<p>There are no transactions yet.</p>
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
@extends(templates/layout_normal)
|
|
||||||
|
|
||||||
@section(main)
|
|
||||||
<h2>No event found</h2>
|
|
||||||
<div class="box compactBox">
|
|
||||||
<p class="error justify">No upcoming or recent event was found. <a href="<?= Container::$routeCollection->getRoute('home')->generateLink() ?>" title="<?= $_ENV['APP_NAME'] ?>">Back to start.</a></p>
|
|
||||||
</div>
|
|
||||||
@endsection
|
|
@ -1,54 +0,0 @@
|
|||||||
@extends(templates/layout_normal)
|
|
||||||
|
|
||||||
@section(main)
|
|
||||||
<h2>
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a> »
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Events</a> »
|
|
||||||
<?= $event->getTitle() ?>
|
|
||||||
<span class="small">[<a href="<?= Container::$routeCollection->getRoute('community.event.edit')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) ?>">edit</a>]</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="gridContainer marginTop">
|
|
||||||
<div>
|
|
||||||
<?php
|
|
||||||
$mainCurrencyCode = $community->getMainCurrency()->getCode();
|
|
||||||
$mainCurrencyRoundDigits = $community->getMainCurrency()->getRoundDigits();
|
|
||||||
?>
|
|
||||||
<h3 class="marginBottom">Finances</h3>
|
|
||||||
<p><a href="<?= Container::$routeCollection->getRoute('community.transactions')->generateLink(['communitySlug' => $community->getSlug(), 'event' => $event->getSlug()]) ?>">Transactions</a> | <a href="<?= Container::$routeCollection->getRoute('community.transactions.new')->generateLink(['communitySlug' => $community->getSlug(), 'event' => $event->getSlug()]) ?>">New transaction</a></p>
|
|
||||||
<table class="fullWidth marginTop">
|
|
||||||
<tr>
|
|
||||||
<td class="bold">Total cost</td>
|
|
||||||
<td class="mono" style="text-align: right;"><?= number_format($totalCost, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<table class="fullWidth marginTop">
|
|
||||||
<tr>
|
|
||||||
<td class="bold">You owe*</td>
|
|
||||||
<td class="mono red" style="text-align: right;"><?= number_format($debtBalance, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
|
|
||||||
</tr>
|
|
||||||
<?php foreach ($debtItems as $item): ?>
|
|
||||||
<tr>
|
|
||||||
<td class="small"><?= $item['payee']->getUser()->getDisplayName() ?></td>
|
|
||||||
<td class="small mono red" style="text-align: right;"><?= number_format($item['amount'], $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<tr>
|
|
||||||
<td class="bold">You're owed*</td>
|
|
||||||
<td class="mono green" style="text-align: right;"><?= number_format($outstandingBalance, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
|
|
||||||
</tr>
|
|
||||||
<?php foreach ($outstandingItems as $item): ?>
|
|
||||||
<tr>
|
|
||||||
<td class="small"><?= $item['payer']->getUser()->getDisplayName() ?></td>
|
|
||||||
<td class="small mono green" style="text-align: right;"><?= number_format($item['amount'], $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<tr>
|
|
||||||
<td class="bold">Your balance*</td>
|
|
||||||
<td class="mono <?= $balance < 0 ? 'red' : ($balance > 0 ? 'green' : '') ?>" style="text-align: right;;"><?= number_format($balance, $mainCurrencyRoundDigits) ?> <?= $mainCurrencyCode ?></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<p class="small right">* Virtual balance only for this event. Check <a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>">community</a> finances for your real balance.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endsection
|
|
@ -1,40 +0,0 @@
|
|||||||
@extends(templates/layout_normal)
|
|
||||||
|
|
||||||
@section(main)
|
|
||||||
<h2>
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a> »
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug()]) ?>">Events</a> »
|
|
||||||
<?php if (isset($event)): ?>
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) ?>"><?= $event->getTitle() ?></a> » Edit event
|
|
||||||
<?php else: ?>
|
|
||||||
New event
|
|
||||||
<?php endif; ?>
|
|
||||||
</h2>
|
|
||||||
<div class="box compactBox">
|
|
||||||
<?php
|
|
||||||
$formAction = isset($event) ?
|
|
||||||
Container::$routeCollection->getRoute('community.event.edit-action')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) :
|
|
||||||
Container::$routeCollection->getRoute('community.events.new-action')->generateLink(['communitySlug' => $community->getSlug()]);
|
|
||||||
?>
|
|
||||||
<form id="eventForm" action="<?= $formAction ?>" method="post" data-redirect-on-success="true">
|
|
||||||
<p class="formLabel">Title</p>
|
|
||||||
<input type="text" class="text big fullWidth" name="title" value="<?= isset($event) ? $event->getTitle() : '' ?>" required>
|
|
||||||
<p class="formLabel marginTop">Description</p>
|
|
||||||
<textarea class="text big fullWidth" name="description" rows="3"><?= isset($event) ? $event->getDescription() : '' ?></textarea>
|
|
||||||
<p class="formLabel marginTop">Start</p>
|
|
||||||
<input type="datetime-local" class="text big fullWidth" name="start" value="<?= isset($event) ? $event->getStartDate()->format('Y-m-d\TH:i') : '' ?>" required>
|
|
||||||
<p class="formLabel marginTop">End</p>
|
|
||||||
<input type="datetime-local" class="text big fullWidth" name="end" value="<?= isset($event) ? $event->getEndDate()->format('Y-m-d\TH:i') : '' ?>" required>
|
|
||||||
<p class="formError justify marginTop"></p>
|
|
||||||
<div class="right marginTop" style="font-size: 0;">
|
|
||||||
<button type="submit" name="submit_button"><?= isset($event) ? '<i class="fa-regular fa-floppy-disk"></i> Save' : '<i class="fa-regular fa-plus"></i> Create' ?></button>
|
|
||||||
<?php if (isset($event)): ?>
|
|
||||||
<button type="submit" form="deleteEvent" name="submit_button" data-confirmation="Are you sure you want to delete this event? All linked transactions will be unlinked!" class="red marginLeft"><i class="fa-regular fa-trash-can"></i> Delete</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<?php if (isset($event)): ?>
|
|
||||||
<form id="deleteEvent" action="<?= Container::$routeCollection->getRoute('community.event.delete-action')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) ?>" method="post" data-redirect-on-success="<?= Container::$routeCollection->getRoute('community.events')->generateLink(['communitySlug' => $community->getSlug()]) ?>"></form>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
@endsection
|
|
@ -1,41 +0,0 @@
|
|||||||
@extends(templates/layout_normal)
|
|
||||||
|
|
||||||
@section(main)
|
|
||||||
<h2>
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a> »
|
|
||||||
Events
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.events.new')->generateLink(['communitySlug' => $community->getSlug()]) ?>">New event</a></p>
|
|
||||||
|
|
||||||
<?php if ($numberOfEvents > 0): ?>
|
|
||||||
<?php
|
|
||||||
$paginationRouteId = 'community.events';
|
|
||||||
$paginationRouteParams = ['communitySlug' => $community->getSlug()];
|
|
||||||
?>
|
|
||||||
|
|
||||||
<?php if ($pages > 1): ?>
|
|
||||||
@include(templates/pagination)
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php foreach ($events as $event): ?>
|
|
||||||
<a class="block" href="<?= Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $community->getSlug(), 'eventSlug' => $event->getSlug()]) ?>">
|
|
||||||
<div class="box">
|
|
||||||
<p style="font-weight: bold;"><?= $event->getTitle() ?></p>
|
|
||||||
<p class="small"><?= $event->getStartDate()->format('Y-m-d') ?> – <?= $event->getEndDate()->format('Y-m-d') ?></p>
|
|
||||||
<p class="small"><?= $event->getDescription() ?></p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
|
|
||||||
<?php if ($pages > 1): ?>
|
|
||||||
@include(templates/pagination)
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<p class="marginTop"><a href="<?= Container::$routeCollection->getRoute('community.events.new')->generateLink(['communitySlug' => $community->getSlug()]) ?>">New event</a></p>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="box">
|
|
||||||
<p>There are no events yet.</p>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
@endsection
|
|
@ -9,26 +9,20 @@
|
|||||||
</h3>
|
</h3>
|
||||||
<?php if (count($communities) > 0): ?>
|
<?php if (count($communities) > 0): ?>
|
||||||
<?php foreach ($communities as $community): ?>
|
<?php foreach ($communities as $community): ?>
|
||||||
<p><a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $community->getSlug()]) ?>"><?= $community->getName() ?></a></p>
|
<p><a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communityId' => $community->getId()]) ?>"><?= $community->getName() ?></a></p>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p>You have no community.</p>
|
<p>You have no community.</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="marginBottom">Upcoming and recent events</h3>
|
<h3 class="marginBottom">Upcoming events</h3>
|
||||||
<?php if (count($upcomingAndRecentEvents) > 0): ?>
|
<?php if (count($upcomingEvents) > 0): ?>
|
||||||
<?php foreach ($upcomingAndRecentEvents as $event): ?>
|
<?php foreach ($upcomingEvents as $event): ?>
|
||||||
<p>
|
<!-- todo -->
|
||||||
<a href="<?= Container::$routeCollection->getRoute('community.event')->generateLink(['communitySlug' => $event->getCommunity()->getSlug(), 'eventSlug' => $event->getSlug()]) ?>"><?= $event->getTitle() ?></a>
|
|
||||||
<span class="small">
|
|
||||||
(<a href="<?= Container::$routeCollection->getRoute('community')->generateLink(['communitySlug' => $event->getCommunity()->getSlug()]) ?>"><?= $event->getCommunity()->getName() ?></a>)
|
|
||||||
<?= $event->getStartDate()->format('Y-m-d') ?> – <?= $event->getEndDate()->format('Y-m-d') ?>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p>There is no event to show.</p>
|
<p>There is no upcoming event.</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
@extends(templates/layout_normal)
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
@section(main)
|
@section(main)
|
||||||
<h2>Login with Google</h2>
|
<h2>Login up with Google</h2>
|
||||||
<div class="box compactBox">
|
<div class="box compactBox">
|
||||||
<p class="error justify"><?= $error ?></p>
|
<p class="error justify"><?= $error ?></p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,12 +10,12 @@
|
|||||||
<input type="password" class="text big fullWidth" name="password" autocomplete="current-password" required minlength="6">
|
<input type="password" class="text big fullWidth" name="password" autocomplete="current-password" required minlength="6">
|
||||||
<p id="loginFormError" class="formError justify marginTop"></p>
|
<p id="loginFormError" class="formError justify marginTop"></p>
|
||||||
<div class="right marginTop">
|
<div class="right marginTop">
|
||||||
<button type="submit"><i class="fa-solid fa-arrow-right-to-bracket"></i> Login</button>
|
<button type="submit">Login</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="center marginTop"><a href="<?= Container::$routeCollection->getRoute('password.requestReset')->generateLink() ?>" title="Request password reset">Forgot your password?</a></p>
|
<p class="center marginTop"><a href="<?= Container::$routeCollection->getRoute('password.requestReset')->generateLink() ?>" title="Request password reset">Forgot your password?</a></p>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<a class="button yellow" href="<?= Container::$routeCollection->getRoute('login.google')->generateLink() ?>" title="Login with Google"><i class="fa-brands fa-google"></i> Login with Google</a>
|
<a class="button yellow" href="<?= Container::$routeCollection->getRoute('login.google')->generateLink() ?>" title="Login with Google">Login with Google</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<p id="passwordResetFormError" class="formError justify marginTop"></p>
|
<p id="passwordResetFormError" class="formError justify marginTop"></p>
|
||||||
<div class="right marginTop">
|
<div class="right marginTop">
|
||||||
<button type="submit"><i class="fa-solid fa-key"></i> Reset password</button>
|
<button type="submit">Continue</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<input type="password" class="text big fullWidth" name="password_confirm" autocomplete="new-password" required minlength="6">
|
<input type="password" class="text big fullWidth" name="password_confirm" autocomplete="new-password" required minlength="6">
|
||||||
<p id="resetPasswordFormError" class="formError justify marginTop"></p>
|
<p id="resetPasswordFormError" class="formError justify marginTop"></p>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<button class="marginTop" type="submit"><i class="fa-solid fa-key"></i> Reset password</button>
|
<button class="marginTop" type="submit">Reset password</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
|
@ -10,12 +10,14 @@
|
|||||||
</h1>
|
</h1>
|
||||||
<p>
|
<p>
|
||||||
<?php if (Container::$request->user()) : ?>
|
<?php if (Container::$request->user()) : ?>
|
||||||
<span><a href="<?= Container::$routeCollection->getRoute('account')->generateLink() ?>" title="Account"><!--
|
<span><a href="<?= Container::$routeCollection->getRoute('account')->generateLink() ?>" title="Account">
|
||||||
--><i class="fa-regular fa-user"></i> <?= Container::$request->user()->getDisplayName() ?><!--
|
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
||||||
|
<svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||||
|
</svg><!--
|
||||||
|
--><?= Container::$request->user()->getDisplayName() ?><!--
|
||||||
--></a></span><!--
|
--></a></span><!--
|
||||||
--><span><a href="<?= Container::$routeCollection->getRoute('logout')->generateLink() ?>" title="Logout"><!--
|
--><span><a href="<?= Container::$routeCollection->getRoute('logout')->generateLink() ?>" title="Logout">Logout</a></span>
|
||||||
--><i class="fa-solid fa-arrow-right-from-bracket"></i> Logout</a><!--
|
|
||||||
--></span>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
<p class="paginateContainer marginTop">
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute($paginationRouteId)->generateLink(array_merge($paginationRouteParams, ['page' => 1])) ?>">«</a>
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute($paginationRouteId)->generateLink(array_merge($paginationRouteParams, ['page' => max(1, $currentPage - 1)])) ?>">‹</a>
|
|
||||||
<?php
|
|
||||||
$maxPages = 7;
|
|
||||||
if ($pages <= $maxPages) {
|
|
||||||
$start = 1;
|
|
||||||
$end = $pages;
|
|
||||||
} else {
|
|
||||||
$maxAdditionalPages = $maxPages - 1;
|
|
||||||
$maxAdditionalPagesHalf = ceil($maxAdditionalPages / 2);
|
|
||||||
$start = $currentPage - $maxAdditionalPagesHalf > 1 ?
|
|
||||||
($currentPage - $maxAdditionalPagesHalf < $pages - $maxAdditionalPages ?
|
|
||||||
$currentPage - $maxAdditionalPagesHalf :
|
|
||||||
$pages - $maxAdditionalPages) :
|
|
||||||
1;
|
|
||||||
$end = $start + $maxAdditionalPages < $pages ?
|
|
||||||
$start + $maxAdditionalPages :
|
|
||||||
$pages;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<?php for ($i = $start; $i <= $end; $i++): ?>
|
|
||||||
<?php if ($currentPage == $i): ?>
|
|
||||||
<span class="selected"><?= $i ?></span>
|
|
||||||
<?php else: ?>
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute($paginationRouteId)->generateLink(array_merge($paginationRouteParams, ['page' => $i])) ?>"><?= $i ?></a>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endfor; ?>
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute($paginationRouteId)->generateLink(array_merge($paginationRouteParams, ['page' => min($pages, $currentPage + 1)])) ?>">›</a>
|
|
||||||
<a href="<?= Container::$routeCollection->getRoute($paginationRouteId)->generateLink(array_merge($paginationRouteParams, ['page' => $pages])) ?>">»</a>
|
|
||||||
</p>
|
|
@ -5,7 +5,7 @@
|
|||||||
<meta name="description" content="<?= $_ENV['APP_NAME'] ?>">
|
<meta name="description" content="<?= $_ENV['APP_NAME'] ?>">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="theme-color" content="#212f4d">
|
<meta name="theme-color" content="#212f4d">
|
||||||
<title><?= isset($title) ? $title . ' – ' : '' ?><?= $_ENV['APP_NAME'] ?></title>
|
<title><?= $_ENV['APP_NAME'] ?></title>
|
||||||
<?php if (preg_match('/^(http(s)?:)?\/\//', $_ENV['STATIC_ROOT']) === 1): ?>
|
<?php if (preg_match('/^(http(s)?:)?\/\//', $_ENV['STATIC_ROOT']) === 1): ?>
|
||||||
<link href="<?= $_ENV['STATIC_ROOT'] ?>" rel="preconnect">
|
<link href="<?= $_ENV['STATIC_ROOT'] ?>" rel="preconnect">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@ -18,10 +18,6 @@
|
|||||||
<link href="https://fonts.googleapis.com/css2?family=Oxygen:wght@400;700&display=block" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Oxygen:wght@400;700&display=block" rel="stylesheet">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Oxygen+Mono:wght@400&display=block" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Oxygen+Mono:wght@400&display=block" rel="stylesheet">
|
||||||
<link href="<?= $_ENV['STATIC_ROOT'] ?>/css/rvr.css?rev=<?= REVISION ?>" rel="stylesheet">
|
<link href="<?= $_ENV['STATIC_ROOT'] ?>/css/rvr.css?rev=<?= REVISION ?>" rel="stylesheet">
|
||||||
<link href="<?= $_ENV['STATIC_ROOT'] ?>/node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css?rev=<?= REVISION ?>" rel="stylesheet">
|
|
||||||
<link href="<?= $_ENV['STATIC_ROOT'] ?>/node_modules/@fortawesome/fontawesome-free/css/solid.min.css?rev=<?= REVISION ?>" rel="stylesheet">
|
|
||||||
<link href="<?= $_ENV['STATIC_ROOT'] ?>/node_modules/@fortawesome/fontawesome-free/css/regular.min.css?rev=<?= REVISION ?>" rel="stylesheet">
|
|
||||||
<link href="<?= $_ENV['STATIC_ROOT'] ?>/node_modules/@fortawesome/fontawesome-free/css/brands.min.css?rev=<?= REVISION ?>" rel="stylesheet">
|
|
||||||
@yields('externalCss')
|
@yields('externalCss')
|
||||||
@yields('inlineCss')
|
@yields('inlineCss')
|
||||||
<link rel="icon" type="image/png" sizes="192x192" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/192x192.png?rev=<?= REVISION ?>">
|
<link rel="icon" type="image/png" sizes="192x192" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/192x192.png?rev=<?= REVISION ?>">
|
||||||
|
40
web.php
40
web.php
@ -7,20 +7,19 @@ use SokoWeb\Request\Request;
|
|||||||
use SokoWeb\Request\Session;
|
use SokoWeb\Request\Session;
|
||||||
use RVR\Controller\HomeController;
|
use RVR\Controller\HomeController;
|
||||||
use RVR\Controller\LoginController;
|
use RVR\Controller\LoginController;
|
||||||
use RVR\Controller\OAuthSessionController;
|
use RVR\Controller\OAuthAuthController;
|
||||||
use RVR\Controller\OAuthController;
|
use RVR\Controller\OAuthController;
|
||||||
use RVR\Controller\UserController;
|
use RVR\Controller\UserController;
|
||||||
use RVR\Controller\UserSearchController;
|
use RVR\Controller\UserSearchController;
|
||||||
use RVR\Controller\CommunityController;
|
use RVR\Controller\CommunityController;
|
||||||
use RVR\Controller\TransactionController;
|
use RVR\Controller\TransactionController;
|
||||||
use RVR\Controller\EventRedirectController;
|
|
||||||
use RVR\Controller\EventController;
|
|
||||||
use RVR\Repository\UserRepository;
|
use RVR\Repository\UserRepository;
|
||||||
|
|
||||||
require 'app.php';
|
require 'app.php';
|
||||||
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
if (!empty($_ENV['DEV'])) {
|
if (!empty($_ENV['DEV'])) {
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
ini_set('display_errors', '1');
|
ini_set('display_errors', '1');
|
||||||
} else {
|
} else {
|
||||||
ini_set('display_errors', '0');
|
ini_set('display_errors', '0');
|
||||||
@ -29,7 +28,6 @@ if (!empty($_ENV['DEV'])) {
|
|||||||
Container::$routeCollection = new RouteCollection();
|
Container::$routeCollection = new RouteCollection();
|
||||||
|
|
||||||
Container::$routeCollection->get('home', '', [HomeController::class, 'getHome']);
|
Container::$routeCollection->get('home', '', [HomeController::class, 'getHome']);
|
||||||
Container::$routeCollection->get('oauth-config-root', '.well-known/openid-configuration', [OAuthController::class, 'getConfig']);
|
|
||||||
Container::$routeCollection->group('login', function (RouteCollection $routeCollection) {
|
Container::$routeCollection->group('login', function (RouteCollection $routeCollection) {
|
||||||
$routeCollection->get('login', '', [LoginController::class, 'getLoginForm']);
|
$routeCollection->get('login', '', [LoginController::class, 'getLoginForm']);
|
||||||
$routeCollection->post('login-action', '', [LoginController::class, 'login']);
|
$routeCollection->post('login-action', '', [LoginController::class, 'login']);
|
||||||
@ -37,10 +35,8 @@ Container::$routeCollection->group('login', function (RouteCollection $routeColl
|
|||||||
$routeCollection->get('login.google-action', 'google/code', [LoginController::class, 'loginWithGoogle']);
|
$routeCollection->get('login.google-action', 'google/code', [LoginController::class, 'loginWithGoogle']);
|
||||||
});
|
});
|
||||||
Container::$routeCollection->group('oauth', function (RouteCollection $routeCollection) {
|
Container::$routeCollection->group('oauth', function (RouteCollection $routeCollection) {
|
||||||
$routeCollection->get('oauth.auth', 'auth', [OAuthSessionController::class, 'auth']);
|
$routeCollection->get('oauth.auth', 'auth', [OAuthAuthController::class, 'auth']);
|
||||||
$routeCollection->post('oauth.token', 'token', [OAuthController::class, 'generateToken']);
|
$routeCollection->post('oauth.token', 'token', [OAuthController::class, 'getToken']);
|
||||||
$routeCollection->post('oauth.token.introspect', 'token/introspect', [OAuthController::class, 'introspectToken']);
|
|
||||||
$routeCollection->post('oauth.token.revoke', 'token/revoke', [OAuthController::class, 'revokeToken']);
|
|
||||||
$routeCollection->get('oauth.userinfo', 'userinfo', [OAuthController::class, 'getUserInfo']);
|
$routeCollection->get('oauth.userinfo', 'userinfo', [OAuthController::class, 'getUserInfo']);
|
||||||
$routeCollection->get('oauth.config', '.well-known/openid-configuration', [OAuthController::class, 'getConfig']);
|
$routeCollection->get('oauth.config', '.well-known/openid-configuration', [OAuthController::class, 'getConfig']);
|
||||||
$routeCollection->get('oauth.certs', 'certs', [OAuthController::class, 'getCerts']);
|
$routeCollection->get('oauth.certs', 'certs', [OAuthController::class, 'getCerts']);
|
||||||
@ -56,28 +52,18 @@ Container::$routeCollection->get('logout', 'logout', [LoginController::class, 'l
|
|||||||
Container::$routeCollection->group('account', function (RouteCollection $routeCollection) {
|
Container::$routeCollection->group('account', function (RouteCollection $routeCollection) {
|
||||||
$routeCollection->get('account', '', [UserController::class, 'getAccount']);
|
$routeCollection->get('account', '', [UserController::class, 'getAccount']);
|
||||||
$routeCollection->post('account-action', '', [UserController::class, 'saveAccount']);
|
$routeCollection->post('account-action', '', [UserController::class, 'saveAccount']);
|
||||||
$routeCollection->get('account.googleConnect', 'googleConnect', [UserController::class, 'getGoogleConnectRedirect']);
|
|
||||||
$routeCollection->get('account.googleConnect-confirm', 'googleConnect/code', [UserController::class, 'getGoogleConnectConfirm']);
|
|
||||||
$routeCollection->post('account.googleConnect-action', 'googleConnect', [UserController::class, 'connectGoogle']);
|
|
||||||
$routeCollection->get('account.googleDisconnect', 'googleDisconnect', [UserController::class, 'getGoogleDisconnectConfirm']);
|
|
||||||
$routeCollection->post('account.googleDisconnect-action', 'googleDisconnect', [UserController::class, 'disconnectGoogle']);
|
|
||||||
$routeCollection->get('account.googleAuthenticate', 'googleAuthenticate', [UserController::class, 'getGoogleAuthenticateRedirect']);
|
$routeCollection->get('account.googleAuthenticate', 'googleAuthenticate', [UserController::class, 'getGoogleAuthenticateRedirect']);
|
||||||
$routeCollection->get('account.googleAuthenticate-action', 'googleAuthenticate/code', [UserController::class, 'authenticateWithGoogle']);
|
$routeCollection->get('account.googleAuthenticate-action', 'googleAuthenticate/code', [UserController::class, 'authenticateWithGoogle']);
|
||||||
});
|
});
|
||||||
Container::$routeCollection->get('searchUser', 'searchUser', [UserSearchController::class, 'searchUser']);
|
Container::$routeCollection->get('searchUser', 'searchUser', [UserSearchController::class, 'searchUser']);
|
||||||
Container::$routeCollection->group('now', function (RouteCollection $routeCollection) {
|
|
||||||
$routeCollection->get('now.event', '', [EventRedirectController::class, 'getEvent']);
|
|
||||||
$routeCollection->get('now.transactions.new', 'transaction', [EventRedirectController::class, 'getEventNewTransaction']);
|
|
||||||
});
|
|
||||||
Container::$routeCollection->group('communities', function (RouteCollection $routeCollection) {
|
Container::$routeCollection->group('communities', function (RouteCollection $routeCollection) {
|
||||||
$routeCollection->get('community.new', 'new', [CommunityController::class, 'getCommunityNew']);
|
$routeCollection->get('community.new', 'new', [CommunityController::class, 'getCommunityNew']);
|
||||||
$routeCollection->post('community.new-action', 'new', [CommunityController::class, 'saveCommunity']);
|
$routeCollection->post('community.new-action', 'new', [CommunityController::class, 'saveCommunity']);
|
||||||
$routeCollection->group('{communitySlug}', function (RouteCollection $routeCollection) {
|
$routeCollection->group('{communityId}', function (RouteCollection $routeCollection) {
|
||||||
$routeCollection->get('community', '', [CommunityController::class, 'getCommunityHome']);
|
$routeCollection->get('community', '', [CommunityController::class, 'getCommunityHome']);
|
||||||
$routeCollection->get('community.settings', 'settings', [CommunityController::class, 'getCommunitySettings']);
|
$routeCollection->get('community.settings', 'settings', [CommunityController::class, 'getCommunitySettings']);
|
||||||
$routeCollection->get('community.edit', 'edit', [CommunityController::class, 'getCommunityEdit']);
|
$routeCollection->get('community.edit', 'edit', [CommunityController::class, 'getCommunityEdit']);
|
||||||
$routeCollection->post('community.edit-action', 'edit', [CommunityController::class, 'saveCommunity']);
|
$routeCollection->post('community.edit-action', 'edit', [CommunityController::class, 'saveCommunity']);
|
||||||
$routeCollection->post('community.delete-action', 'delete', [CommunityController::class, 'deleteCommunity']);
|
|
||||||
$routeCollection->get('community.members', 'members', [CommunityController::class, 'getMembersEdit']);
|
$routeCollection->get('community.members', 'members', [CommunityController::class, 'getMembersEdit']);
|
||||||
$routeCollection->post('community.members.new-action', 'members/new', [CommunityController::class, 'saveMember']);
|
$routeCollection->post('community.members.new-action', 'members/new', [CommunityController::class, 'saveMember']);
|
||||||
$routeCollection->post('community.members.edit-action', 'members/edit', [CommunityController::class, 'saveMember']);
|
$routeCollection->post('community.members.edit-action', 'members/edit', [CommunityController::class, 'saveMember']);
|
||||||
@ -100,25 +86,13 @@ Container::$routeCollection->group('communities', function (RouteCollection $rou
|
|||||||
$routeCollection->post('community.transactions.edit-action', '{transactionId}', [TransactionController::class, 'saveTransaction']);
|
$routeCollection->post('community.transactions.edit-action', '{transactionId}', [TransactionController::class, 'saveTransaction']);
|
||||||
$routeCollection->post('community.transactions.delete-action', '{transactionId}/delete', [TransactionController::class, 'deleteTransaction']);
|
$routeCollection->post('community.transactions.delete-action', '{transactionId}/delete', [TransactionController::class, 'deleteTransaction']);
|
||||||
});
|
});
|
||||||
$routeCollection->group('events', function (RouteCollection $routeCollection) {
|
|
||||||
$routeCollection->get('community.events', '', [EventController::class, 'getEvents']);
|
|
||||||
$routeCollection->get('community.events.new', 'new', [EventController::class, 'getEventEdit']);
|
|
||||||
$routeCollection->post('community.events.new-action', 'new', [EventController::class, 'saveEvent']);
|
|
||||||
$routeCollection->get('community.events.search', 'search', [EventController::class, 'searchEvent']);
|
|
||||||
$routeCollection->group('{eventSlug}', function (RouteCollection $routeCollection) {
|
|
||||||
$routeCollection->get('community.event', '', [EventController::class, 'getEvent']);
|
|
||||||
$routeCollection->get('community.event.edit', 'edit', [EventController::class, 'getEventEdit']);
|
|
||||||
$routeCollection->post('community.event.edit-action', 'edit', [EventController::class, 'saveEvent']);
|
|
||||||
$routeCollection->post('community.event.delete-action', 'delete', [EventController::class, 'deleteEvent']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Container::$sessionHandler = new DatabaseSessionHandler(
|
Container::$sessionHandler = new DatabaseSessionHandler(
|
||||||
Container::$dbConnection,
|
Container::$dbConnection,
|
||||||
'sessions',
|
'sessions',
|
||||||
new DateTime('-1 days')
|
new DateTime('-7 days')
|
||||||
);
|
);
|
||||||
|
|
||||||
session_set_save_handler(Container::$sessionHandler, true);
|
session_set_save_handler(Container::$sessionHandler, true);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user