Compare commits
24 Commits
4752fedc5c
...
46fc2c978f
Author | SHA1 | Date |
---|---|---|
Mal | 46fc2c978f | |
Mal | ef7f7c4b4a | |
Mal | 6b2abc1f89 | |
Mal | 37f220b561 | |
Mal | 39d14451b2 | |
Mal | e1a62442d9 | |
Mal | 44aaff929c | |
Mal | a42f728ccf | |
Mal | f4fdf50288 | |
Mal | 3a99e99130 | |
Mal | 3cb02a12f1 | |
Mal | b0f2991346 | |
Mal | f6d1376dbb | |
Mal | 86c12890c2 | |
Mal | ec485fb03d | |
Mal | 459be4a999 | |
Mal | 0334c7022d | |
Mal | f2957ab4ad | |
Mal | 515dadb319 | |
Mal | d5aa47ff54 | |
Mal | 0a8c71b735 | |
Mal | 3a1a026e2f | |
Mal | 5df0a1f8c2 | |
Mal | a7d9c0bf51 |
|
@ -1,5 +1,6 @@
|
|||
data/classes/Setting.php
|
||||
data/cache
|
||||
data/tmp
|
||||
data/qr
|
||||
backend/classes/Setting.php
|
||||
backend/cache
|
||||
backend/tmp
|
||||
backend/qr
|
||||
api/docs
|
||||
.idea
|
||||
|
|
13
Makefile
13
Makefile
|
@ -1,6 +1,11 @@
|
|||
build:
|
||||
php data/scripts/generate.php
|
||||
repair:
|
||||
php backend/scripts/repair.php
|
||||
|
||||
setup:
|
||||
php backend/scripts/setup.php
|
||||
|
||||
cache:
|
||||
php backend/scripts/generate.php
|
||||
|
||||
clean:
|
||||
rm -rf data/cache/* data/tmp/*
|
||||
|
||||
rm -rf backend/cache/* backend/tmp/*
|
||||
|
|
38
README.md
38
README.md
|
@ -1,19 +1,27 @@
|
|||
# Ringfinger
|
||||
A restfull keyring API with web UI to share your jabber fingerprints with the people you want.
|
||||
|
||||
Create an account, store your jabber fingerprints and share it with your friends. There is no need to type in your 64 chars long fingerprint for each of your friends. Just enter it once and decide which people you want to share your keyring with.
|
||||
# Ringfinger
|
||||
A restfull keyring API with web UI to share your OMEMO fingerprints for jabber with the people you like.
|
||||
|
||||
Create an account, store your OMEMO fingerprints and share it with your friends. There is no need to type in your 64 chars long fingerprint for each of your friends. Just enter it once and decide which people you want to share your keyring with.
|
||||
|
||||
# Requirements
|
||||
* A webserver (Nginx, Apache, etc.)
|
||||
* PHP 7.4 (with php-fpm configured)
|
||||
* MySQL/MariaDB
|
||||
* qrencode
|
||||
|
||||
# Setup
|
||||
Clone or copy the ringfinger folder to the location folder of your webserver. Move into the ringfinger folder an generate the cache:
|
||||
> make build
|
||||
|
||||
## Webserver configuration
|
||||
Despite your basic setup with PHP and MySQL/MariaDB your webserver has to to rewrite all requests that access the path `/ringfinger/api/v1/...` to `/ringfinger/api/v1/index.php` to make the API working.
|
||||
|
||||
### NGINX
|
||||
Add the following line to your nginx.conf or to a separate file that will be included by the nginx.conf:
|
||||
|
||||
> rewrite /ringfinger/api/v1/.* /ringfinger/api/v1/index.php;
|
||||
|
||||
**Also make sure you deny the access to /ringfinger/data for all!**
|
||||
Before you start setting up make sure you have an empty MySQL database created. You also need a database user that has full access to the newly created database!
|
||||
|
||||
Then clone or copy the ringfinger folder to the location folder of your webserver. Move into the ringfinger folder and start the setup process
|
||||
> make setup
|
||||
|
||||
## Webserver configuration
|
||||
Despite your basic setup with PHP and MySQL/MariaDB your webserver has to to rewrite all requests that access the path `/ringfinger/api/v1/...` to `/ringfinger/api/v1/index.php` to make the API working.
|
||||
|
||||
### NGINX
|
||||
Add the following line to your nginx.conf or to a separate file that will be included by the nginx.conf:
|
||||
|
||||
> rewrite /ringfinger/api/v1/.* /ringfinger/api/v1/index.php;
|
||||
|
||||
**Also make sure you deny the access to /ringfinger/backend for all!**
|
||||
|
|
|
@ -0,0 +1,610 @@
|
|||
openapi: 3.0.0
|
||||
|
||||
info:
|
||||
title: Ringfinger API
|
||||
version: 1.0.0
|
||||
description: The restfull API of the jabber fingerprint keyring.
|
||||
|
||||
tags:
|
||||
- name: User
|
||||
description: A user that can login, store fingerprints and share them with other users.
|
||||
- name: Fingerprint
|
||||
description: The jabber fingerprint owned by a user.
|
||||
- name: Sharing
|
||||
description: The sharing of all fingerprints of a user with another user.
|
||||
|
||||
paths:
|
||||
'/api/v1/user/{userId}':
|
||||
get:
|
||||
tags:
|
||||
- User
|
||||
summary: Returns the user attributes
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/UserId'
|
||||
responses:
|
||||
200:
|
||||
description: Returns the user data for the given id.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
success:
|
||||
$ref: '#/components/schemas/Success'
|
||||
result:
|
||||
type: object
|
||||
description: Contains the user data.
|
||||
properties:
|
||||
userId:
|
||||
$ref: '#/components/schemas/UserId'
|
||||
usernamme:
|
||||
$ref: '#/components/schemas/Username'
|
||||
jabberAddress:
|
||||
$ref: '#/components/schemas/JabberAddress'
|
||||
isAdmin:
|
||||
type: boolean
|
||||
description: Returns if the user has admin permissions.
|
||||
example: true
|
||||
fingerprintIds:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
description: A list of all fingerprint ids of the user.
|
||||
example: [25, 42, 88]
|
||||
default: []
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
put:
|
||||
tags:
|
||||
- User
|
||||
summary: Changes user attributes
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/UserId'
|
||||
requestBody:
|
||||
required: true
|
||||
description: Contains the json with the user attributes that have to be changed.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
username:
|
||||
$ref: '#/components/schemas/Username'
|
||||
password:
|
||||
$ref: '#/components/schemas/Password'
|
||||
email:
|
||||
$ref: '#/components/schemas/EmailAddress'
|
||||
jabberAddress:
|
||||
$ref: '#/components/schemas/JabberAddress'
|
||||
responses:
|
||||
200:
|
||||
$ref: '#/components/responses/Success'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
delete:
|
||||
tags:
|
||||
- User
|
||||
summary: Deletes a user
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/UserId'
|
||||
responses:
|
||||
200:
|
||||
$ref: '#/components/responses/Success'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
|
||||
'/api/v1/user/{userId}/fingerprints':
|
||||
get:
|
||||
tags:
|
||||
- User
|
||||
summary: A list containing all fingerprints of the user
|
||||
responses:
|
||||
200:
|
||||
description: Returns the success state and a list with all fingerprints of the user.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
success:
|
||||
$ref: '#/components/schemas/Success'
|
||||
fingerprints:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
example:
|
||||
- fingerprintId: 8
|
||||
fingerprint: '5BDF1668E59F2184582591699F55D9158DEF400A48772887A8F61531ED36B2A'
|
||||
userId: 25
|
||||
- fingerprintId: 42
|
||||
fingerprint: '6FF8842B6D17F5C2098A3DD8AB55D9158DEF400A48772887A8F61531ED36B2A'
|
||||
userId: 25
|
||||
|
||||
'/api/v1/user/{userId}/email':
|
||||
get:
|
||||
tags:
|
||||
- User
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/UserId'
|
||||
summary: Returns the user's email address
|
||||
responses:
|
||||
200:
|
||||
description: Contains a JSON object with the success state and the email address.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
success:
|
||||
$ref: '#/components/schemas/Success'
|
||||
email:
|
||||
$ref: '#/components/schemas/EmailAddress'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
put:
|
||||
tags:
|
||||
- User
|
||||
summary: Changes the user's email address
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/UserId'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
$ref: '#/components/schemas/EmailAddress'
|
||||
allOf:
|
||||
- required:
|
||||
- email
|
||||
responses:
|
||||
200:
|
||||
description: Returns the success state for the change request.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
success:
|
||||
$ref: '#/components/schemas/Success'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
|
||||
'/api/v1/user/{userId}/password':
|
||||
put:
|
||||
tags:
|
||||
- User
|
||||
summary: Changes the password of the user
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/UserId'
|
||||
requestBody:
|
||||
description: The password inside a json object.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
password:
|
||||
type: string
|
||||
description: The new password.
|
||||
example: '12345'
|
||||
allOf:
|
||||
- required:
|
||||
- password
|
||||
responses:
|
||||
200:
|
||||
$ref: '#/components/responses/Success'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
|
||||
'/api/v1/user/{userId}/admin':
|
||||
post:
|
||||
tags:
|
||||
- User
|
||||
summary: Gives the user admin permissions
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/UserId'
|
||||
responses:
|
||||
200:
|
||||
$ref: '#/components/responses/Success'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
delete:
|
||||
tags:
|
||||
- User
|
||||
summary: Removes the user's admin permissions
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/UserId'
|
||||
responses:
|
||||
200:
|
||||
$ref: '#/components/responses/Success'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
|
||||
'/api/v1/user':
|
||||
post:
|
||||
tags:
|
||||
- User
|
||||
summary: Creates a new user
|
||||
requestBody:
|
||||
required: true
|
||||
description: 'The attributes for the user to be created.'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
username:
|
||||
$ref: '#/components/schemas/Username'
|
||||
password:
|
||||
$ref: '#/components/schemas/Password'
|
||||
email:
|
||||
$ref: '#/components/schemas/EmailAddress'
|
||||
jabberAddress:
|
||||
$ref: '#/components/schemas/JabberAddress'
|
||||
allOf:
|
||||
- required:
|
||||
- username
|
||||
- password
|
||||
- email
|
||||
- jabberAddress
|
||||
responses:
|
||||
200:
|
||||
$ref: '#/components/responses/Success'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
|
||||
'/api/v1/user/session':
|
||||
post:
|
||||
tags:
|
||||
- User
|
||||
summary: Creates a user session (login)
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
username:
|
||||
$ref: '#/components/schemas/Username'
|
||||
password:
|
||||
$ref: '#/components/schemas/Password'
|
||||
allOf:
|
||||
- required:
|
||||
- username
|
||||
- password
|
||||
responses:
|
||||
200:
|
||||
description: Returns the success state and the user id for the created session.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
success:
|
||||
$ref: '#/components/schemas/Success'
|
||||
userId:
|
||||
$ref: '#/components/schemas/UserId'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
delete:
|
||||
tags:
|
||||
- User
|
||||
summary: Deletes a user session (logout)
|
||||
responses:
|
||||
200:
|
||||
$ref: '#/components/responses/Success'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
|
||||
'/api/v1/fingerprint/{fingerprintId}/qr':
|
||||
get:
|
||||
tags:
|
||||
- Fingerprint
|
||||
summary: Returns the fingerprint's QR code
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/FingerprintId'
|
||||
responses:
|
||||
200:
|
||||
description: The QR code as SVG image
|
||||
content:
|
||||
application/svg+xml:
|
||||
schema:
|
||||
type: string
|
||||
description: The xml of the SVG image.
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
|
||||
'/api/v1/fingerprint/{fingerprintId}':
|
||||
get:
|
||||
tags:
|
||||
- Fingerprint
|
||||
summary: Returns the attributes of the fingerprint
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/FingerprintId'
|
||||
responses:
|
||||
200:
|
||||
description: Returns the fingerprint for the given fingerprint id.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FingerprintData'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
put:
|
||||
tags:
|
||||
- Fingerprint
|
||||
summary: Changes the fingerprint
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/FingerprintId'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
fingerprint:
|
||||
$ref: '#/components/schemas/Fingerprint'
|
||||
required:
|
||||
- fingerprint
|
||||
responses:
|
||||
200:
|
||||
description: Returns the success state.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
description: 'Returns the success state of the fingerprint change.'
|
||||
example: true
|
||||
message:
|
||||
type: string
|
||||
description: 'Returns if no changes have been found inside the request body.'
|
||||
example: 'Fingerprint did not differ from the stored. Nothing changed.'
|
||||
required:
|
||||
- success
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
|
||||
delete:
|
||||
tags:
|
||||
- Fingerprint
|
||||
summary: Removes the fingerprint
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/FingerprintId'
|
||||
responses:
|
||||
200:
|
||||
$ref: '#/components/responses/Success'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
|
||||
'/api/v1/fingerprint':
|
||||
post:
|
||||
tags:
|
||||
- Fingerprint
|
||||
summary: Creates a new fingerprint
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
description: The parameters for the new fingerprint to be created.
|
||||
properties:
|
||||
fingerprint:
|
||||
$ref: '#/components/schemas/Fingerprint'
|
||||
userId:
|
||||
$ref: '#/components/schemas/UserId'
|
||||
required:
|
||||
- fingerprint
|
||||
- userId
|
||||
|
||||
responses:
|
||||
200:
|
||||
description: Returns the confirmation and the id for the newly created fingerprint.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
description: Returns the success status.
|
||||
example: true
|
||||
fingerprintId:
|
||||
type: integer
|
||||
description: The id for the newly created fingerprint.
|
||||
example: 42
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
|
||||
'/api/v1/sharing/{sharingId}':
|
||||
get:
|
||||
tags:
|
||||
- Sharing
|
||||
summary: Returns the sharing attributes
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/SharingId'
|
||||
responses:
|
||||
200:
|
||||
description: The JSON result for the sharing.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
success:
|
||||
$ref: '#/components/schemas/Success'
|
||||
result:
|
||||
type: object
|
||||
properties:
|
||||
sharingId:
|
||||
$ref: '#/components/schemas/SharingId'
|
||||
userId:
|
||||
type: integer
|
||||
description: The id of the user that shares his fingerprints.
|
||||
example: 42
|
||||
userSharedId:
|
||||
$ref: '#/components/schemas/SharingUserId'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
delete:
|
||||
tags:
|
||||
- Sharing
|
||||
summary: Removes the specified sharing
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/SharingId'
|
||||
responses:
|
||||
200:
|
||||
$ref: '#/components/responses/Success'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
|
||||
'/api/v1/sharing':
|
||||
post:
|
||||
tags:
|
||||
- Sharing
|
||||
summary: Creates a new sharing
|
||||
requestBody:
|
||||
description: Contains the attributes for the sharing to be created.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
userId:
|
||||
type: integer
|
||||
description: The id of the user that shares his fingerprints.
|
||||
example: 42
|
||||
userSharedId:
|
||||
$ref: '#/components/schemas/SharingUserId'
|
||||
allOf:
|
||||
- required:
|
||||
- userId
|
||||
- userSharedId
|
||||
|
||||
responses:
|
||||
200:
|
||||
description: Returns the success flag and the id for the newly created sharing.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
success:
|
||||
$ref: '#/components/schemas/Success'
|
||||
sharingId:
|
||||
$ref: '#/components/schemas/SharingId'
|
||||
default:
|
||||
$ref: '#/components/responses/Error'
|
||||
|
||||
components:
|
||||
parameters:
|
||||
FingerprintId:
|
||||
name: fingerprintId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/components/schemas/FingerprintId'
|
||||
|
||||
SharingId:
|
||||
name: sharingId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/components/schemas/SharingId'
|
||||
|
||||
UserId:
|
||||
name: userId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/components/schemas/UserId'
|
||||
|
||||
responses:
|
||||
Success:
|
||||
description: Returns that the request could be handled successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
success:
|
||||
$ref: '#/components/schemas/Success'
|
||||
|
||||
Error:
|
||||
description: Returns a JSON object that describes the error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
schemas:
|
||||
Error:
|
||||
type: object
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
description: Returns that the request couldn't be handled.
|
||||
example: false
|
||||
message:
|
||||
type: string
|
||||
description: Description of the error that occured.
|
||||
example: Something extremely terrible happened!
|
||||
|
||||
Success:
|
||||
type: boolean
|
||||
description: Returns if the request could be handled.
|
||||
example: true
|
||||
|
||||
UserId:
|
||||
type: integer
|
||||
description: The id of the user in question.
|
||||
example: 42
|
||||
|
||||
Username:
|
||||
type: string
|
||||
description: The login name for the given user.
|
||||
example: Kevin42
|
||||
|
||||
Password:
|
||||
type: string
|
||||
description: The password for the user login.
|
||||
example: '12345'
|
||||
|
||||
JabberAddress:
|
||||
type: string
|
||||
description: The address of the jabber account.
|
||||
example: donalt@jabber.de
|
||||
|
||||
EmailAddress:
|
||||
type: string
|
||||
description: The registered email address for the user.
|
||||
example: kevin42@gmail.com
|
||||
|
||||
FingerprintId:
|
||||
type: integer
|
||||
description: The id that identifies the fingerprint.
|
||||
example: 42
|
||||
readOnly: true
|
||||
|
||||
Fingerprint:
|
||||
type: string
|
||||
description: The jabber fingerprint
|
||||
example: '5BDF1668E59F2184582591699F55D9158DEF400A48772887A8F61531ED36B2A'
|
||||
|
||||
FingerprintData:
|
||||
type: object
|
||||
properties:
|
||||
fingerprintId:
|
||||
$ref: '#/components/schemas/FingerprintId'
|
||||
fingerprint:
|
||||
$ref: '#/components/schemas/Fingerprint'
|
||||
userId:
|
||||
type: integer
|
||||
description: The id of the user that owns this fingerprint.
|
||||
example: 25
|
||||
|
||||
SharingId:
|
||||
type: integer
|
||||
description: The id for the sharing.
|
||||
example: 42
|
||||
readOnly: true
|
||||
|
||||
SharingUserId:
|
||||
type: integer
|
||||
description: The id of the user who is allowed to display the fingerprints.
|
||||
example: 25
|
|
@ -1,15 +1,15 @@
|
|||
<?php
|
||||
|
||||
require '../../data/classes/core/Autoloader.php';
|
||||
require '../../backend/classes/core/Autoloader.php';
|
||||
|
||||
$autoloader = new Autoloader('../../data/cache/');
|
||||
$autoloader = new Autoloader('../../backend/cache/');
|
||||
|
||||
$session = new Session();
|
||||
|
||||
$router = new Router($_SERVER['REQUEST_URI'], $_SERVER['REQUEST_METHOD']);
|
||||
|
||||
if (isset($_SERVER['HTTP_CONTENT_TYPE'])) {
|
||||
$router->setRequestBody($_SERVER['HTTP_CONTENT_TYPE'], file_get_contents('php://input'));
|
||||
$router->setRequestBody($_SERVER['HTTP_CONTENT_TYPE'], file_get_contents('php://input'));
|
||||
}
|
||||
|
||||
$router->route();
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class ApiBadRequestResponse extends ApiResponse
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setParameter('success', false);
|
||||
$this->setStatus(self::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class ApiJsonResponse extends ApiResponse
|
||||
{
|
||||
protected string $mimeType = MimeType::JSON;
|
||||
|
||||
public function __construct(int $status = ServerStatus::OK)
|
||||
{
|
||||
parent::__construct($status);
|
||||
|
||||
$this->setParameter('success', true);
|
||||
}
|
||||
|
||||
public function setResult(JsonSerializable $result): void
|
||||
{
|
||||
$this->setParameter('result', $result->jsonSerialize());
|
||||
}
|
||||
|
||||
public function setSuccess(bool $success): void
|
||||
{
|
||||
$this->setParameter('success', $success);
|
||||
}
|
||||
|
||||
public function respond(): void
|
||||
{
|
||||
parent::respond();
|
||||
|
||||
echo json_encode($this->parameters);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class ApiResponse implements JsonSerializable
|
||||
{
|
||||
public const STATUS_OK = 200;
|
||||
public const STATUS_FORBIDDEN = 403;
|
||||
public const STATUS_UNAUTHORIZED = 401;
|
||||
public const STATUS_BAD_REQUEST = 400;
|
||||
public const STATUS_NOT_FOUND = 404;
|
||||
public const STATUS_SERVER_ERROR = 500;
|
||||
|
||||
public const MIME_TYPE_PLAINTEXT = 'text/plain';
|
||||
public const MIME_TYPE_JSON = 'application/json';
|
||||
public const MIME_TYPE_SVG = 'image/svg+xml';
|
||||
|
||||
protected int $status = ServerStatus::OK;
|
||||
protected string $mimeType = MimeType::PLAINTEXT;
|
||||
protected array $parameters = [];
|
||||
|
||||
public function __construct(int $status = ServerStatus::OK)
|
||||
{
|
||||
$this->setStatus($status);
|
||||
}
|
||||
|
||||
public function setParameter(string $key, $value): void
|
||||
{
|
||||
$this->parameters[$key] = $value;
|
||||
}
|
||||
|
||||
public function setStatus(int $status): void
|
||||
{
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
public function setMessage(string $message): void
|
||||
{
|
||||
$this->setParameter('message', $message);
|
||||
}
|
||||
|
||||
public function setMimeType(string $mimeType): void
|
||||
{
|
||||
$this->mimeType = $mimeType;
|
||||
}
|
||||
|
||||
public function setBody(JsonSerializable $data): void
|
||||
{
|
||||
$this->parameters = $data->jsonSerialize();
|
||||
}
|
||||
|
||||
public function SetMessageIdNotFound(string $instanceName): void
|
||||
{
|
||||
$this->setMessage(sprintf('Die für %s angeforderte ID existiert nicht!', $instanceName));
|
||||
}
|
||||
|
||||
public function getStatus(): int
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function getMimeType(): string
|
||||
{
|
||||
return $this->mimeType;
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
public function respond(): void
|
||||
{
|
||||
http_response_code($this->status);
|
||||
header('Content-Type: ' . $this->mimeType);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class ApiSuccessResponse extends ApiResponse
|
||||
{
|
||||
public function __construct(bool $success = true)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setParameter('success', $success);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class ApiSvgResponse extends ApiResponse
|
||||
{
|
||||
private string $content;
|
||||
protected string $mimeType = MimeType::SVG;
|
||||
|
||||
public function __construct(int $status = ServerStatus::OK)
|
||||
{
|
||||
parent::__construct($status);
|
||||
}
|
||||
|
||||
public function setContent(string $content): void
|
||||
{
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
public function respond(): void
|
||||
{
|
||||
parent::respond();
|
||||
header('Content-Length: ' . strlen($this->content));
|
||||
echo $this->content;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class ApiUnauthorizedResponse extends ApiResponse
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setStatus(self::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class FingerprintDeleteController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/fingerprint/{fingerprintId}';
|
||||
|
||||
private int $fingerprintId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
|
||||
$this->fingerprintId = (int)$this->getUrlParamInt('fingerprintId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (!$this->isUserLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::handle();
|
||||
|
||||
if ($this->response->getStatus() !== ServerStatus::OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$db = new MySqlDatabase();
|
||||
$this->response = new ApiJsonResponse();
|
||||
|
||||
$fingerprint = new Fingerprint($this->fingerprintId, $db);
|
||||
|
||||
if (!$this->hasUserPermission($fingerprint->getUserId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$db->startTransaction();
|
||||
|
||||
if (!$this->hasUserPermission($fingerprint->getUserId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$qrCode = new QrCode($fingerprint->getFingerprintId(), $fingerprint->getFingerprint());
|
||||
$fingerprint->Delete();
|
||||
$qrCode->delete();
|
||||
|
||||
$db->commit();
|
||||
} catch (Throwable $e) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class FingerprintGetController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/fingerprint/{fingerprintId}';
|
||||
|
||||
private int $fingerprintId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
|
||||
$this->fingerprintId = (int)$this->getUrlParamInt('fingerprintId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
if (!$this->isUserLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fingerprint = new Fingerprint($this->fingerprintId);
|
||||
|
||||
if (!$this->hasUserPermission($fingerprint->getUserId())) {
|
||||
if (!$fingerprint->isSharedWith($this->session->getUserId())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
$this->response->setResult($fingerprint);
|
||||
} catch (Throwable $e) {
|
||||
$this->response = new ApiJsonResponse(ApiResponse::STATUS_NOT_FOUND);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage(sprintf('No fingerprint with id %d found!', $this->fingerprintId));
|
||||
$this->response->setMimeType(ApiResponse::MIME_TYPE_JSON);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class FingerprintPostController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/fingerprint';
|
||||
protected array $mandatoryAttributes = [
|
||||
'fingerprint',
|
||||
'userId',
|
||||
];
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
parent::handle();
|
||||
|
||||
if (!$this->isUserLoggedIn() || $this->response->getStatus() !== ServerStatus::OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
$db = new MySqlDatabase();
|
||||
|
||||
$fingerprint = new Fingerprint(null, $db);
|
||||
$this->response = new ApiJsonResponse();
|
||||
|
||||
try {
|
||||
if (!$this->hasUserPermission((int)$this->jsonBody->userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fingerprint->setFingerprint((string)$this->jsonBody->fingerprint);
|
||||
$fingerprint->setUserId((int)$this->jsonBody->userId);
|
||||
|
||||
if (!$db->hasTransaction()) {
|
||||
$db->startTransaction();
|
||||
}
|
||||
|
||||
$fingerprint->Save();
|
||||
|
||||
$qrCode = new QrCode($fingerprint->getFingerprintId(), $fingerprint->getFingerprint());
|
||||
$qrCode->generate();
|
||||
$qrCode->save();
|
||||
|
||||
$db->commit();
|
||||
|
||||
$this->response->setParameter('fingerprintId', $fingerprint->getFingerprintId());
|
||||
} catch (QrCodeException $e) {
|
||||
$this->response->setStatus(ServerStatus::INTERNAL_ERROR);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage('An error occured during QR code creation!');
|
||||
} catch (Throwable $e) {
|
||||
$this->catchDatabaseException($e->getMessage(), $this->jsonBody);
|
||||
}
|
||||
}
|
||||
|
||||
private function catchDatabaseException(string $message, object $json): void
|
||||
{
|
||||
$this->response->setParameter('success', false);
|
||||
|
||||
if (substr_count($message, 'foreign key constraint fails') > 0) {
|
||||
$this->response->setMessage(sprintf('User with id %d doesn\'t exist!', $json->userId));
|
||||
$this->response->setStatus(ServerStatus::NOT_FOUND);
|
||||
} elseif (substr_count($message, 'Duplicate entry') > 0) {
|
||||
$this->response->setMessage(sprintf('Fingerprint %s already exists!', $json->fingerprint));
|
||||
$this->response->setStatus(ServerStatus::BAD_REQUEST);
|
||||
} else {
|
||||
$this->response->setMessage($message);
|
||||
$this->response->setStatus(ServerStatus::INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class FingerprintPutController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/fingerprint/{fingerprintId}';
|
||||
protected array $mandatoryAttributes = [
|
||||
'fingerprint',
|
||||
];
|
||||
|
||||
private int $fingerprintId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
|
||||
$this->fingerprintId = (int)$this->getUrlParamInt('fingerprintId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (!$this->isUserLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::handle();
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
|
||||
try {
|
||||
$fingerprint = new Fingerprint($this->fingerprintId);
|
||||
|
||||
if ($this->hasUserPermission($fingerprint->getUserId()) || $this->handleFingerprint($fingerprint)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->setMessage('Fingerprint did not differ from the stored. Nothing changed.');
|
||||
} catch (Throwable $e) {
|
||||
$this->response->setStatus(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function handleFingerprint(Fingerprint $fingerprint): bool
|
||||
{
|
||||
if ($fingerprint->getFingerprint() !== $this->jsonBody->fingerprint) {
|
||||
$fingerprint->setFingerprint($this->jsonBody->fingerprint);
|
||||
$fingerprint->Save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class QrCodeGetController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/fingerprint/{fingerprintId}/qr';
|
||||
|
||||
private int $fingerprintId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
|
||||
$this->fingerprintId = (int)$this->getUrlParamInt('fingerprintId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$fingerprint = new Fingerprint($this->fingerprintId);
|
||||
|
||||
if (!$this->isUserLoggedIn() || !$this->hasUserPermission($fingerprint->getUserId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filename = Setting::PATH_QR_CODES . (string)$this->fingerprintId . '.svg';
|
||||
|
||||
if (!is_file($filename)) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage(sprintf('No QR code for fingerprint id %d found!', $this->fingerprintId));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response = new ApiSvgResponse();
|
||||
|
||||
$file = @fopen($filename, 'r');
|
||||
$this->response->setContent(@fread($file, @filesize($filename)));
|
||||
@fclose($file);
|
||||
} catch (Throwable $e) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::INTERNAL_ERROR);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class SharingDeleteController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/sharing/{sharingId}';
|
||||
|
||||
private int $sharingId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
|
||||
$this->sharingId = (int)$this->getUrlParamInt('sharingId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
parent::handle();
|
||||
|
||||
if ($this->response->getStatus() !== ServerStatus::OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$sharing = new Sharing($this->sharingId);
|
||||
|
||||
if (!$this->isUserLoggedIn() || !$this->hasUserPermission($sharing->getUserId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sharing->Delete();
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
$this->response->setParameter('success', true);
|
||||
} catch (Throwable $e) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class SharingGetController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/sharing/{sharingId}';
|
||||
|
||||
private int $sharingId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
|
||||
$this->sharingId = (int)$this->getUrlParamInt('sharingId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
parent::handle();
|
||||
|
||||
if ($this->response->getStatus() !== ServerStatus::OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$sharing = new Sharing($this->sharingId);
|
||||
|
||||
if (!$this->isUserLoggedIn() || !$this->hasUserPermission($sharing->getUserId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
$this->response->setParameter('success', true);
|
||||
$this->response->setResult($sharing);
|
||||
} catch (Throwable $e) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class SharingPostController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/sharing';
|
||||
|
||||
/** @var string[] */
|
||||
protected array $mandatoryAttributes = [
|
||||
'userId',
|
||||
'userSharedId',
|
||||
];
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
parent::handle();
|
||||
|
||||
if ($this->response->getStatus() !== ServerStatus::OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->isUserLoggedIn() || !$this->hasUserPermission($this->jsonBody->userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$sharing = new Sharing();
|
||||
$sharing->setUserId($this->jsonBody->userId);
|
||||
$sharing->setUserShared($this->jsonBody->userSharedId);
|
||||
$sharing->Save();
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
$this->response->setParameter('sharingId', $sharing->getSharingId());
|
||||
} catch (Throwable $e) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserAdminDeleteController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user/{userId}/admin';
|
||||
|
||||
private int $userId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
$this->userId = (int)$this->getUrlParamInt('userId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$this->response = new ApiJsonResponse();
|
||||
|
||||
if (!$this->isUserLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->session->isAdmin()) {
|
||||
$this->response->setStatus(ServerStatus::UNAUTHORIZED);
|
||||
$this->response->setSuccess(false);
|
||||
$this->response->setMessage('You have no permission!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$user = new User($this->userId);
|
||||
$user->setAdmin(false);
|
||||
$user->Save();
|
||||
} catch (Throwable $e) {
|
||||
$this->response->setSuccess(false);
|
||||
$this->response->setStatus($e->getCode() !== 0 ? $e->getCode() : ServerStatus::BAD_REQUEST);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserAdminPostController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user/{userId}/admin';
|
||||
|
||||
private int $userId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
$this->userId = (int)$this->getUrlParamInt('userId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$this->response = new ApiJsonResponse();
|
||||
|
||||
if (!$this->isUserLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->session->isAdmin()) {
|
||||
$this->response->setStatus(ServerStatus::UNAUTHORIZED);
|
||||
$this->response->setSuccess(false);
|
||||
$this->response->setMessage('You have no permission!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$user = new User($this->userId);
|
||||
$user->setAdmin(true);
|
||||
$user->Save();
|
||||
} catch (Throwable $e) {
|
||||
$this->response->setSuccess(false);
|
||||
$this->response->setStatus($e->getCode() !== 0 ? $e->getCode() : ServerStatus::BAD_REQUEST);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserDeleteController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user/{userId}';
|
||||
|
||||
private int $userId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
|
||||
$this->userId = (int)$this->getUrlParamInt('userId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (!$this->isUserLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::handle();
|
||||
|
||||
if ($this->response->getStatus() !== ServerStatus::OK || !$this->hasUserPermission($this->userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$user = new User($this->userId);
|
||||
$user->Delete();
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
$this->response->setParameter('success', true);
|
||||
} catch (Throwable $e) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserEmailGetController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user/{userId}/email';
|
||||
|
||||
private int $userId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
|
||||
$this->userId = (int)$this->getUrlParamInt('userId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (!$this->isUserLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::handle();
|
||||
|
||||
if (!$this->hasUserPermission($this->userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$user = new User($this->userId);
|
||||
$this->response->setParameter('email', $user->getEmail());
|
||||
} catch (Throwable $e) {
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
$this->response->setStatus($e->getCode() !== 0 ? $e->getCode() : ServerStatus::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserEmailPutController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user/{userId}/email';
|
||||
protected array $mandatoryAttributes = [
|
||||
'email',
|
||||
];
|
||||
|
||||
private int $userId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
$this->userId = (int)$this->getUrlParamInt('userId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (!$this->isUserLoggedIn() || !$this->hasUserPermission($this->userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::handle();
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
|
||||
try {
|
||||
$json = json_decode($this->requestBody);
|
||||
|
||||
$user = new User($this->userId);
|
||||
|
||||
if ($user->getEmail() !== $json->email) {
|
||||
$user->setEmail($json->email);
|
||||
$user->Save();
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->response->setStatus($e->getCode() !== 0 ? $e->getCode() : ServerStatus::BAD_REQUEST);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserFingerprintsGetController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user/{userId}/fingerprints';
|
||||
|
||||
private int $userId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
$this->userId = (int)$this->getUrlParamInt('userId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (!$this->isUserLoggedIn() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->response = new ApiJsonResponse();
|
||||
|
||||
$user = new User($this->userId);
|
||||
|
||||
if (!$this->hasUserPermission($this->userId)) {
|
||||
if (!$user->isSharingWith($this->session->getUserId())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$fingerprints = new FingerprintCollection();
|
||||
|
||||
foreach ($user->getFingerprintIds() as $fingerprintId) {
|
||||
$fingerprints->add(new Fingerprint($fingerprintId));
|
||||
}
|
||||
|
||||
$this->response->setParameter('fingerprints', $fingerprints->jsonSerialize());
|
||||
} catch (Throwable $e) {
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
$this->response->setStatus($e->getCode() !== 0 ? $e->getCode() : ServerStatus::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserGetController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user/{userId}';
|
||||
|
||||
private int $userId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
|
||||
$this->userId = (int)$this->getUrlParamInt('userId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (!$this->isUserLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
|
||||
try {
|
||||
$user = new User($this->userId);
|
||||
$this->response->setResult($user);
|
||||
} catch (Throwable $e) {
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setStatus($e->getCode() !== 0 ? $e->getCode() : ServerStatus::INTERNAL_ERROR);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserPasswordPutController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user/{userId}/password';
|
||||
|
||||
/** @var string[] */
|
||||
protected array $mandatoryAttributes= [
|
||||
'password',
|
||||
];
|
||||
|
||||
private int $userId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
$this->userId = (int)$this->getUrlParamInt('userId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (!$this->isUserLoggedIn() || !$this->hasUserPermission($this->userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::handle();
|
||||
|
||||
if ($this->response->getStatus() !== ServerStatus::OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
|
||||
try {
|
||||
$user = new User($this->userId);
|
||||
$user->setPassword(Password::GetHash($this->jsonBody->password));
|
||||
$user->Save();
|
||||
} catch (Throwable $e) {
|
||||
$this->response->setSuccess(false);
|
||||
$this->response->setStatus($e->getCode() !== 0 ? $e->getCode() : ServerStatus::BAD_REQUEST);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserPostController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user';
|
||||
protected array $mandatoryAttributes = [
|
||||
'username',
|
||||
'password',
|
||||
'email',
|
||||
'jabberAddress',
|
||||
];
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (!$this->isUserLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->session->isAdmin()) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::UNAUTHORIZED);
|
||||
$this->response->setSuccess(false);
|
||||
$this->response->setMessage('You have no permission!');
|
||||
}
|
||||
|
||||
parent::handle();
|
||||
|
||||
if ($this->response->getStatus() !== ServerStatus::OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$user = new User();
|
||||
|
||||
$user->setUsername($this->jsonBody->username);
|
||||
$user->setPassword(Password::GetHash($this->jsonBody->password));
|
||||
$user->setEmail($this->jsonBody->email);
|
||||
$user->setJabberAddress($this->jsonBody->jabberAddress);
|
||||
$user->Save();
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
$this->response->setParameter('userId', $user->getUserId());
|
||||
} catch (DatabaseException $e) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::INTERNAL_ERROR);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
} catch (Throwable $e) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserPutController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user/{userId}';
|
||||
|
||||
private int $userId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
|
||||
$this->userId = (int)$this->getUrlParamInt('userId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (!$this->isUserLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::handle();
|
||||
|
||||
if ($this->response->getStatus() !== ServerStatus::OK || !$this->hasUserPermission($this->userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$user = new User($this->userId);
|
||||
|
||||
$hasChanged = $this->handleUserData($user);
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
|
||||
if ($hasChanged) {
|
||||
$user->Save();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response->setMessage('No differing attributes found. Nothing changed.');
|
||||
} catch (Throwable $e) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function setUsername(User $user): bool
|
||||
{
|
||||
$hasChanged = $user->getUsername() !== $this->jsonBody->username;
|
||||
$user->setUsername($this->jsonBody->username);
|
||||
|
||||
return $hasChanged;
|
||||
}
|
||||
|
||||
private function setPassword(User $user): bool
|
||||
{
|
||||
$hasChanged = !Password::IsValid($this->jsonBody->password, $user->getPassword());
|
||||
$user->setPassword(Password::GetHash($this->jsonBody->password));
|
||||
|
||||
return $hasChanged;
|
||||
}
|
||||
|
||||
|
||||
private function setEmail(User $user): bool
|
||||
{
|
||||
$hasChanged = $user->getEmail() !== $this->jsonBody->email;
|
||||
$user->setEmail($this->jsonBody->email);
|
||||
|
||||
return $hasChanged;
|
||||
}
|
||||
|
||||
private function setJabberAddress(User $user): bool
|
||||
{
|
||||
$hasChanged = $user->getJabberAddress() !== $this->jsonBody->jabberAddress;
|
||||
$user->setJabberAddress($this->jsonBody->jabberAddress);
|
||||
|
||||
return $hasChanged;
|
||||
}
|
||||
|
||||
private function handleUserData(User $user): bool
|
||||
{
|
||||
$hasChanged = $this->setUsername($user) || false;
|
||||
$hasChanged = $this->setPassword($user) || $hasChanged;
|
||||
$hasChanged = $this->setEmail($user) || $hasChanged;
|
||||
$hasChanged = $this->setJabberAddress($user) || $hasChanged;
|
||||
|
||||
return $hasChanged;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserSessionDeleteController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user/session';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
parent::handle();
|
||||
|
||||
if (!$this->session->IsLoggedIn()) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage('No session to delete!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->session->Destroy();
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
$this->response->setParameter('success', true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserSessionPostController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user/session';
|
||||
protected array $mandatoryAttributes = [
|
||||
'username',
|
||||
'password',
|
||||
];
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
parent::handle();
|
||||
|
||||
if ($this->response->getStatus() !== ServerStatus::OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->session->IsLoggedIn()) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage('You are already logged in!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->session->Login($this->jsonBody->username, $this->jsonBody->password)) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::UNAUTHORIZED);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage('Login failed!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
$this->response->setParameter('userId', $this->session->getUserId());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
abstract class AbstractController
|
||||
{
|
||||
protected string $route;
|
||||
protected ApiResponse $response;
|
||||
|
||||
protected Session $session;
|
||||
protected string $requestUrl;
|
||||
protected ?string $requestBody = null;
|
||||
protected ?object $jsonBody = null;
|
||||
protected ?string $contentType = null;
|
||||
|
||||
/** @var string[] */
|
||||
protected array $mandatoryAttributes = [];
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
$this->requestUrl = $url;
|
||||
$this->response = new ApiResponse();
|
||||
$this->session = new Session();
|
||||
}
|
||||
|
||||
public function setRequestBody(string $contentType, string $content): void
|
||||
{
|
||||
$this->requestBody = $content;
|
||||
$this->contentType = $contentType;
|
||||
}
|
||||
|
||||
public function getResponse(): ApiResponse
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (!$this->validateJsonBody()) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setSuccess(false);
|
||||
$this->response->setMessage('The request body has not the required json attributes!');
|
||||
}
|
||||
}
|
||||
|
||||
protected function getUrlParam(string $name): ?string
|
||||
{
|
||||
foreach (explode('/', $this->route) as $index => $fragment) {
|
||||
if ($fragment === '{' . $name . '}') {
|
||||
return explode('/', $this->requestUrl)[$index];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getUrlParamInt(string $name): ?int
|
||||
{
|
||||
$param = $this->getUrlParam($name);
|
||||
|
||||
return $param !== null ? (int)$param : null;
|
||||
}
|
||||
|
||||
public function isUserLoggedIn(): bool
|
||||
{
|
||||
if (!$this->session->IsLoggedIn()) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::UNAUTHORIZED);
|
||||
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage('You are not logged in!');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function hasUserPermission(int $userId): bool
|
||||
{
|
||||
$this->response = new ApiJsonResponse();
|
||||
|
||||
$hasPermission = $this->session->isAdmin() || $this->session->getUserId() === $userId;
|
||||
|
||||
if (!$hasPermission) {
|
||||
$this->response->setSuccess(false);
|
||||
$this->response->setMessage('You don\'t have the permission!');
|
||||
$this->response->setStatus(ServerStatus::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
return $hasPermission;
|
||||
}
|
||||
|
||||
protected function validateJsonBody(): bool
|
||||
{
|
||||
if (count($this->mandatoryAttributes) === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->contentType === MimeType::JSON && $this->requestBody === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$json = json_decode($this->requestBody);
|
||||
|
||||
foreach ($this->mandatoryAttributes as $attribute) {
|
||||
if (!isset($json->{$attribute})) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->jsonBody = $json;
|
||||
|
||||
return true;
|
||||
} catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class Autoloader
|
||||
{
|
||||
private const PATH_CLASSES = 'backend/classes';
|
||||
private const PATH_CONTROLLERS = self::PATH_CLASSES . '/controller';
|
||||
private const PATH_CACHE = 'backend/cache/';
|
||||
|
||||
public function __construct(string $cachePath = self::PATH_CACHE)
|
||||
{
|
||||
if ($cachePath !== self::PATH_CACHE) {
|
||||
$cachePath = substr($cachePath, -1) === '/' ? $cachePath : $cachePath . '/';
|
||||
}
|
||||
|
||||
$routesFound = @include($cachePath . 'routes.php');
|
||||
$classesFound = @include($cachePath . 'classes.php');
|
||||
|
||||
if (!$routesFound || !$classesFound) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'Autoloader cache not found! Please generate it with %s::BuildCache() at first!',
|
||||
self::class
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
spl_autoload_register(
|
||||
function (string $className) {
|
||||
if (!$this->loadClass($className)) {
|
||||
throw new Exception(sprintf('Class %s couldn\'t be loaded!', $className));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static function BuildCache(): void
|
||||
{
|
||||
self::BuildClassCache();
|
||||
self::BuildRouteCache();
|
||||
}
|
||||
|
||||
public static function BuildClassCache(): void
|
||||
{
|
||||
$classesResult = self::scanForClasses();
|
||||
|
||||
$cacheContent = '';
|
||||
|
||||
foreach ($classesResult as $className => $path) {
|
||||
$cacheContent .= sprintf("\t\t'%s' => '%s',\n", $className, $path);
|
||||
}
|
||||
|
||||
$cacheContent .= "\t]\n);";
|
||||
|
||||
self::buildCacheFile($cacheContent, 'classes');
|
||||
}
|
||||
|
||||
private function loadClass(string $className): bool
|
||||
{
|
||||
if (!isset(CLASSES[$className]) || !@include(CLASSES[$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function BuildRouteCache(): void
|
||||
{
|
||||
$controllersResult = self::scanForControllers();
|
||||
$controllerMethods = [
|
||||
'GET' => [],
|
||||
'POST' => [],
|
||||
'PUT' => [],
|
||||
'DELETE' => [],
|
||||
];
|
||||
|
||||
foreach ($controllersResult as $className => $path) {
|
||||
$file = fopen($path, 'r');
|
||||
$content = fread($file, filesize($path));
|
||||
fclose($file);
|
||||
|
||||
preg_match_all('/(?<=private )\w+ \$\w+(?=;)/', $content, $matches);
|
||||
|
||||
$params = [];
|
||||
|
||||
foreach ($matches[0] as $match) {
|
||||
$parts = explode(' ', $match);
|
||||
$params[] = [
|
||||
'type' => $parts[0],
|
||||
'name' => $parts[1],
|
||||
];
|
||||
}
|
||||
|
||||
preg_match('/(?<=protected string \$route = \').*(?=\';)/', $content, $matches);
|
||||
$route = $matches[0];
|
||||
|
||||
preg_match('/[A-Z][a-z]+(?=Controller)/', $className, $matches);
|
||||
$method = strtoupper($matches[0]);
|
||||
|
||||
$controllerMethods[$method][$route] = [
|
||||
'name' => $className,
|
||||
'params' => $params,
|
||||
];
|
||||
}
|
||||
|
||||
$cacheContent = '';
|
||||
|
||||
foreach ($controllerMethods as $method => $controllers) {
|
||||
$cacheContent .= self::createRoutesForMethod($method, $controllers);
|
||||
}
|
||||
|
||||
$cacheContent .= "\t]\n);";
|
||||
|
||||
self::buildCacheFile($cacheContent, 'routes');
|
||||
}
|
||||
|
||||
private static function createRoutesForMethod(string $method, array $routes): string
|
||||
{
|
||||
krsort($routes);
|
||||
$stringRoutes = '';
|
||||
|
||||
foreach ($routes as $route => $params) {
|
||||
$stringRoutes .= sprintf(
|
||||
"'%s' => [
|
||||
'controller' => %s::class,
|
||||
'params' => [
|
||||
%s
|
||||
],
|
||||
],
|
||||
",
|
||||
$route,
|
||||
$params['name'],
|
||||
self::createRouteParams($params['params'])
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
"
|
||||
'%s' => [
|
||||
%s
|
||||
],
|
||||
",
|
||||
$method,
|
||||
$stringRoutes,
|
||||
);
|
||||
}
|
||||
|
||||
private static function createRouteParams(array $params): string
|
||||
{
|
||||
$string = '';
|
||||
|
||||
foreach ($params as $param) {
|
||||
$string .= sprintf(
|
||||
"
|
||||
'%s' => [
|
||||
'type' => '%s',
|
||||
],
|
||||
",
|
||||
str_replace('$', '', $param['name']),
|
||||
$param['type']
|
||||
);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
private static function reformatCacheFileContent(string $content): string
|
||||
{
|
||||
$depth = 0;
|
||||
$reformatted = '';
|
||||
$replace = '';
|
||||
|
||||
// Removing indents
|
||||
foreach (explode("\n", $content) as $line) {
|
||||
$trim = trim($line);
|
||||
|
||||
if ($trim !== '') {
|
||||
$replace .= $trim . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = 0; $i < strlen($replace); $i++) {
|
||||
if (in_array($replace[$i], [')', ']'])) {
|
||||
$depth--;
|
||||
}
|
||||
|
||||
if ($replace[$i - 1] === "\n") {
|
||||
$reformatted .= str_repeat("\t", $depth);
|
||||
}
|
||||
|
||||
$reformatted .= $replace[$i];
|
||||
|
||||
if (in_array($replace[$i], ['(', '['])) {
|
||||
$depth++;
|
||||
}
|
||||
}
|
||||
|
||||
return $reformatted;
|
||||
}
|
||||
|
||||
private static function buildCacheFile(string $content, string $cacheName): void
|
||||
{
|
||||
$cacheContent = sprintf(
|
||||
"<?php\n\n/*\n * This file was auto generated on %s\n */\n\ndefine(\n\t'%s',\n\t[\n",
|
||||
(new DateTime())->format('Y-m-d H:i:s'),
|
||||
strtoupper($cacheName)
|
||||
);
|
||||
|
||||
$cacheContent .= $content;
|
||||
|
||||
$file = fopen(getcwd() . '/' . self::PATH_CACHE . $cacheName . '.php', 'w');
|
||||
fwrite($file, self::reformatCacheFileContent($cacheContent));
|
||||
fclose($file);
|
||||
}
|
||||
|
||||
private static function scanForFiles(string $folder): array
|
||||
{
|
||||
$folder = substr($folder, -1) === '/' ? substr($folder, 0, -1) : $folder;
|
||||
$files = [];
|
||||
$handler = opendir($folder);
|
||||
|
||||
while ($file = readdir($handler)) {
|
||||
$path = $folder . '/' . $file;
|
||||
|
||||
if (is_dir($path) && $file !== '.' && $file !== '..') {
|
||||
$files = array_merge($files, self::scanForFiles($path));
|
||||
} elseif (is_file($path) && substr($path, -4) === '.php') {
|
||||
$className = substr($file, 0, -4);
|
||||
$files[$className] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
private static function scanForClasses(): array
|
||||
{
|
||||
return self::scanForFiles(getcwd() . '/' . self::PATH_CLASSES);
|
||||
}
|
||||
|
||||
private static function scanForControllers(): array
|
||||
{
|
||||
return self::scanForFiles(getcwd() . '/' . self::PATH_CONTROLLERS);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
interface DatabaseInterface
|
||||
{
|
||||
public const ORDER_ASC = true;
|
||||
public const ORDER_DESC = false;
|
||||
|
||||
/**
|
||||
* Has to close the connection.
|
||||
*/
|
||||
public function __destruct();
|
||||
|
||||
/**
|
||||
* Sends an sql query to the database.
|
||||
*/
|
||||
public function Query(string $query, array $params = []): void;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getResult(): array;
|
||||
|
||||
/**
|
||||
* Selects backend from a table.
|
||||
*/
|
||||
public function Select(
|
||||
string $tableName,
|
||||
array $fields = [],
|
||||
array $conditions = [],
|
||||
int $limit = 0,
|
||||
array $orderBy = [],
|
||||
bool $asc = true,
|
||||
int $offset = 0
|
||||
): array;
|
||||
|
||||
/**
|
||||
* Deletes rows from a table.
|
||||
*/
|
||||
public function Delete(string $table, array $conditions): void;
|
||||
|
||||
/**
|
||||
* Inserts a new row into the table.
|
||||
*/
|
||||
public function Insert(string $table, array $fields): ?int;
|
||||
|
||||
/**
|
||||
* Edits backend inside a table.
|
||||
*/
|
||||
public function Update(string $table, array $fields, array $conditions): void;
|
||||
|
||||
/**
|
||||
* Returns the number of entries found.
|
||||
*/
|
||||
public function Count(string $table, array $conditions = []): int;
|
||||
|
||||
/*
|
||||
* Returns if there's an open transaction.
|
||||
*/
|
||||
public function hasTransaction(): bool;
|
||||
|
||||
/*
|
||||
* Starts a transaction that can later be committed or rolled back.
|
||||
*/
|
||||
public function startTransaction(): void;
|
||||
|
||||
/*
|
||||
* Quits a current transaction without saving.
|
||||
*/
|
||||
public function rollback(): void;
|
||||
|
||||
/*
|
||||
* Saves and exits a current transaction.
|
||||
*/
|
||||
public function commit(): void;
|
||||
|
||||
/**
|
||||
* Returns the primary key from the last inserted row.
|
||||
*/
|
||||
public function GetLastInsertedId(): int;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class MimeType
|
||||
{
|
||||
public const PLAINTEXT = 'text/plain';
|
||||
public const JSON = 'application/json';
|
||||
public const SVG = 'image/svg+xml';
|
||||
}
|
|
@ -0,0 +1,281 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class MySqlDatabase implements DatabaseInterface
|
||||
{
|
||||
private const CHARS_ALLOWED_IN_TABLE_NAMES = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_-';
|
||||
|
||||
private ?PDO $connection;
|
||||
private ?PDOStatement $cursor;
|
||||
private bool $isTransactionOpen = false;
|
||||
|
||||
public function __construct(
|
||||
string $hostname = Setting::MYSQL_HOST,
|
||||
string $user = Setting::MYSQL_USER,
|
||||
string $password = Setting::MYSQL_PASSWORD,
|
||||
string $database = Setting::MYSQL_DATABASE
|
||||
) {
|
||||
try {
|
||||
$this->connection = new PDO("mysql:host=$hostname;dbname=$database", $user, $password);
|
||||
} catch (Throwable $e) {
|
||||
throw new DatabaseException(
|
||||
'Couldn\'t connect to the database!',
|
||||
ServerStatus::INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->connection = null;
|
||||
}
|
||||
|
||||
public function Query(string $query, array $params = []): void
|
||||
{
|
||||
$this->cursor = $this->connection->prepare($query);
|
||||
|
||||
if (!$this->cursor) {
|
||||
throw new DatabaseException(
|
||||
'Initialization of database cursor failed',
|
||||
DatabaseException::CONNECTION_FAILED
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($params as $key => $param) {
|
||||
if (is_bool($param)) {
|
||||
$param = (int)$param;
|
||||
}
|
||||
|
||||
$this->cursor->bindValue(':' . $key, $param);
|
||||
}
|
||||
|
||||
if (!$this->cursor->execute()) {
|
||||
throw new DatabaseException($this->cursor->errorInfo()[2], $this->cursor->errorInfo()[1]);
|
||||
}
|
||||
}
|
||||
|
||||
public function getResult(): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
while ($fetch = $this->cursor->fetchObject()) {
|
||||
$row = [];
|
||||
|
||||
foreach (get_object_vars($fetch) as $key => $value) {
|
||||
$row[$key] = $value;
|
||||
}
|
||||
|
||||
$result[] = $row;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects backend from a table.
|
||||
*/
|
||||
public function Select(
|
||||
string $tableName,
|
||||
array $fields = [],
|
||||
array $conditions = [],
|
||||
int $limit = 0,
|
||||
array $orderBy = [],
|
||||
bool $asc = true,
|
||||
int $offset = 0
|
||||
): array {
|
||||
if (!self::isValidTableName($tableName)) {
|
||||
[];
|
||||
}
|
||||
|
||||
if (count($fields) === 0) {
|
||||
$fieldsExpression = '*';
|
||||
} else {
|
||||
$fieldsExpression = implode(',', $fields);
|
||||
}
|
||||
|
||||
$conditionsExpression = '';
|
||||
$conditionPairs = [];
|
||||
|
||||
foreach ($conditions as $field => $value) {
|
||||
$conditionPairs[] = sprintf('%s = :%s ', $field, $field);
|
||||
}
|
||||
|
||||
if (count($conditions) > 0) {
|
||||
$conditionsExpression = 'WHERE ';
|
||||
$conditionsExpression .= implode(' AND ', $conditionPairs);
|
||||
}
|
||||
|
||||
$orderStatement = '';
|
||||
|
||||
if (count($orderBy) > 0) {
|
||||
$orderStatement = 'ORDER BY ' . implode(',', $orderBy);
|
||||
|
||||
if (!$asc) {
|
||||
$orderStatement .= ' DESC';
|
||||
}
|
||||
}
|
||||
|
||||
$limitStatement = '';
|
||||
|
||||
if ($limit > 0) {
|
||||
$limitStatement = 'LIMIT ' . $limit;
|
||||
}
|
||||
|
||||
$offsetStatement = '';
|
||||
|
||||
if ($offset > 0) {
|
||||
$offsetStatement = 'OFFSET ' . $offset;
|
||||
}
|
||||
|
||||
$query = sprintf(
|
||||
'SELECT %s FROM %s %s %s %s %s',
|
||||
$fieldsExpression,
|
||||
$tableName,
|
||||
$conditionsExpression,
|
||||
$orderStatement,
|
||||
$limitStatement,
|
||||
$offsetStatement
|
||||
);
|
||||
|
||||
try {
|
||||
$this->Query($query, $conditions);
|
||||
} catch (Throwable $e) {
|
||||
throw new DatabaseException(
|
||||
$e->getMessage(),
|
||||
ServerStatus::INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
return $this->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes rows from a table.
|
||||
*/
|
||||
public function Delete(string $table, array $conditions): void
|
||||
{
|
||||
if (count($conditions) === 0) {
|
||||
$conditionsStatement = '1';
|
||||
} else {
|
||||
$conditionPairs = [];
|
||||
|
||||
foreach ($conditions as $field => $value) {
|
||||
$conditionPairs[] = sprintf('%s=:Condition%s', $field, $field);
|
||||
$conditions['Condition' . $field] = $value;
|
||||
unset($conditions[$field]);
|
||||
}
|
||||
|
||||
$conditionsStatement = implode(' AND ', $conditionPairs);
|
||||
}
|
||||
|
||||
$query = sprintf('DELETE FROM %s WHERE %s', $table, $conditionsStatement);
|
||||
|
||||
$this->Query($query, $conditions);
|
||||
}
|
||||
|
||||
public function Insert(string $table, array $fields): ?int
|
||||
{
|
||||
if (count($fields) === 0) {
|
||||
throw new DatabaseException('Row to insert is empty!');
|
||||
}
|
||||
|
||||
$fieldNames = implode(',', array_keys($fields));
|
||||
$fieldPlaceholder = [];
|
||||
|
||||
foreach ($fields as $name => $value) {
|
||||
$fieldPlaceholder[] = ':' . $name;
|
||||
}
|
||||
|
||||
$query = sprintf(
|
||||
'INSERT INTO %s (%s) VALUES (%s)', $table, $fieldNames, implode(',', $fieldPlaceholder)
|
||||
);
|
||||
|
||||
$this->Query($query, $fields);
|
||||
|
||||
$lastInsertedId = $this->GetLastInsertedId();
|
||||
|
||||
if ((int)$lastInsertedId === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $lastInsertedId;
|
||||
}
|
||||
|
||||
public function Update(string $table, array $fields, array $conditions): void
|
||||
{
|
||||
$conditionPairs = [];
|
||||
|
||||
foreach ($conditions as $field => $value) {
|
||||
$conditionPairs[] = sprintf('%s=:Condition%s', $field, $field);
|
||||
$conditions['Condition' . $field] = $value;
|
||||
unset($conditions[$field]);
|
||||
}
|
||||
|
||||
$conditionsStatement = implode(' AND ', $conditionPairs);
|
||||
|
||||
$fieldPairs = [];
|
||||
|
||||
foreach ($fields as $field => $value) {
|
||||
$fieldPairs[] = sprintf('%s=:%s', $field, $field);
|
||||
}
|
||||
|
||||
$fieldsStatement = implode(',', $fieldPairs);
|
||||
|
||||
$query = sprintf('UPDATE %s SET %s WHERE %s', $table, $fieldsStatement, $conditionsStatement);
|
||||
|
||||
|
||||
$this->Query($query, array_merge($fields, $conditions));
|
||||
}
|
||||
|
||||
public function Count(string $table, array $conditions = []): int
|
||||
{
|
||||
$result = $this->Select($table, ['count(*)'], $conditions);
|
||||
|
||||
return (int)$result[0]['count(*)'];
|
||||
}
|
||||
|
||||
public function hasTransaction(): bool
|
||||
{
|
||||
return $this->isTransactionOpen;
|
||||
}
|
||||
|
||||
public function startTransaction(): void
|
||||
{
|
||||
$this->connection->beginTransaction();
|
||||
$this->isTransactionOpen = true;
|
||||
}
|
||||
|
||||
public function rollback(): void
|
||||
{
|
||||
$this->connection->rollBack();
|
||||
$this->isTransactionOpen = false;
|
||||
}
|
||||
|
||||
public function commit(): void
|
||||
{
|
||||
$this->connection->commit();
|
||||
$this->isTransactionOpen = false;
|
||||
}
|
||||
|
||||
public function GetLastInsertedId(): int
|
||||
{
|
||||
$this->Query('SELECT LAST_INSERT_ID() as ID');
|
||||
|
||||
return (int)$this->getResult()[0]['ID'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a check if the given table name contains forbidden chars.
|
||||
*/
|
||||
private static function isValidTableName(string $tableName): bool
|
||||
{
|
||||
foreach (str_split($tableName) as $char) {
|
||||
if (substr_count(self::CHARS_ALLOWED_IN_TABLE_NAMES, $char) === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
class MySqlTable extends Table
|
||||
{
|
||||
public function __construct(string $tableName, $id = null, DatabaseInterface &$database = null)
|
||||
{
|
||||
self::EnsureConnection($database);
|
||||
|
||||
parent::__construct($tableName, $id, $database);
|
||||
}
|
||||
|
||||
public static function EnsureConnection(?DatabaseInterface & $database): void
|
||||
{
|
||||
if (!($database instanceof MySqlDatabase)) {
|
||||
$database = new MySqlDatabase();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class Password
|
||||
{
|
||||
public static function IsValid(string $password, string $hash): bool
|
||||
{
|
||||
return password_verify($password, $hash);
|
||||
}
|
||||
|
||||
public static function GetHash(string $password): string
|
||||
{
|
||||
return password_hash($password, PASSWORD_BCRYPT);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class QrCode
|
||||
{
|
||||
private int $fingerprintId;
|
||||
private string $fingerprint;
|
||||
private string $temporaryFilename;
|
||||
|
||||
public function __construct(int $fingerprintId, string $fingerprint)
|
||||
{
|
||||
$this->fingerprintId = $fingerprintId;
|
||||
$this->fingerprint = $fingerprint;
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
if (!is_file($this->temporaryFilename)) {
|
||||
throw new QrCodeException(
|
||||
sprintf('Temporary QR file %s couldn\'t be found!', $this->temporaryFilename)
|
||||
);
|
||||
}
|
||||
|
||||
$returnCode = 0;
|
||||
|
||||
$filename = $this->getFilePath();
|
||||
|
||||
passthru(
|
||||
sprintf('mv %s %s', $this->temporaryFilename, $filename),
|
||||
$returnCode
|
||||
);
|
||||
|
||||
if ($returnCode !== 0 || !is_file($filename)) {
|
||||
throw new QrCodeException(
|
||||
sprintf('QR code for fingerprint %d couldn\'t be created!', $this->fingerprintId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(): void
|
||||
{
|
||||
$filepath = $this->getFilePath();
|
||||
|
||||
if (!is_file($filepath)) {
|
||||
throw new QrCodeException(sprintf('Qr code file %s not found!', $filepath));
|
||||
}
|
||||
|
||||
if (!unlink($filepath)) {
|
||||
throw new QrCodeException('Couldn\'t delete %s!', $filepath);
|
||||
}
|
||||
}
|
||||
|
||||
public function generate(): bool
|
||||
{
|
||||
$returnCode = 0;
|
||||
|
||||
$path = substr(Setting::PATH_TMP, -1) === '/' ? Setting::PATH_TMP : Setting::PATH_TMP . '/';
|
||||
|
||||
$this->temporaryFilename = $path . $this->generateTemporaryFilename() . '.svg';
|
||||
|
||||
passthru(
|
||||
sprintf('qrencode -o %s -t SVG "%s"', $this->temporaryFilename, $this->fingerprint),
|
||||
$returnCode
|
||||
);
|
||||
|
||||
return !(bool)$returnCode;
|
||||
}
|
||||
|
||||
public function hasFile(): bool
|
||||
{
|
||||
return is_file($this->getFilePath());
|
||||
}
|
||||
|
||||
private function generateTemporaryFilename(): string
|
||||
{
|
||||
$hash = hash('md5', (new DateTime())->format('U') . $this->fingerprint);
|
||||
|
||||
return sprintf('%s.svg', $hash);
|
||||
}
|
||||
|
||||
private function getFilePath(): string
|
||||
{
|
||||
$path = substr(Setting::PATH_QR_CODES, -1) === '/'
|
||||
? Setting::PATH_QR_CODES
|
||||
: Setting::PATH_QR_CODES . '/';
|
||||
|
||||
return $path . $this->fingerprintId . '.svg';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class Router
|
||||
{
|
||||
private string $route;
|
||||
private string $method;
|
||||
private ?string $requestBody = null;
|
||||
private ?string $contentType = null;
|
||||
|
||||
public function __construct(string $route, string $method)
|
||||
{
|
||||
$this->route = $route;
|
||||
$this->method = $method;
|
||||
}
|
||||
|
||||
public function setRequestBody(string $contentType, string $content): void
|
||||
{
|
||||
$this->contentType = $contentType;
|
||||
$this->requestBody = $content;
|
||||
}
|
||||
|
||||
public function route(): void
|
||||
{
|
||||
foreach (ROUTES[$this->method] as $route => $params) {
|
||||
preg_match_all($this->createRegex($route, $params['params']), $this->route, $matches);
|
||||
|
||||
if (count($matches[0]) > 0) {
|
||||
$class = new ReflectionClass($params['controller']);
|
||||
|
||||
/** @var AbstractController $controller */
|
||||
$controller = $class->newInstance($matches[0][0]);
|
||||
|
||||
if ($this->requestBody !== null && $this->contentType !== null) {
|
||||
$controller->setRequestBody($this->contentType, $this->requestBody);
|
||||
}
|
||||
|
||||
$controller->handle();
|
||||
$controller->getResponse()->respond();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createRegex(string $route, array $params): string
|
||||
{
|
||||
foreach ($params as $param => $values) {
|
||||
switch ($values['type']) {
|
||||
case 'int':
|
||||
$route = str_replace('{' . $param . '}', '[0-9]+', $route);
|
||||
}
|
||||
}
|
||||
|
||||
return '/' . str_replace('/', '\\/', $route) . '/';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class ServerStatus
|
||||
{
|
||||
public const OK = 200;
|
||||
public const FORBIDDEN = 403;
|
||||
public const UNAUTHORIZED = 401;
|
||||
public const BAD_REQUEST = 400;
|
||||
public const NOT_FOUND = 404;
|
||||
public const INTERNAL_ERROR = 500;
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class Session
|
||||
{
|
||||
public const TYPE_INT = 1;
|
||||
public const TYPE_STRING = 2;
|
||||
public const TYPE_BOOL = 3;
|
||||
|
||||
private const IS_LOGGED_IN = 'is_logged_in';
|
||||
private const USER_ID = 'account_id';
|
||||
private const USERNAME = 'username';
|
||||
private const IS_ADMIN = 'admin';
|
||||
private const EMAIL = 'email';
|
||||
private const JABBER_ADDRESS = 'jabber';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@session_start();
|
||||
|
||||
if (!$this->HasValue(self::IS_LOGGED_IN)) {
|
||||
$this->SetBool(self::IS_LOGGED_IN, false);
|
||||
}
|
||||
}
|
||||
|
||||
public function Destroy(): bool
|
||||
{
|
||||
return session_unset() && session_destroy();
|
||||
}
|
||||
|
||||
public function Login(string $usernameOrEmail, string $password): bool
|
||||
{
|
||||
try {
|
||||
$user = User::getFromUsername($usernameOrEmail);
|
||||
} catch (Throwable $e) {
|
||||
$user = User::getFromEmail($usernameOrEmail);
|
||||
}
|
||||
|
||||
if ($user === null || !Password::IsValid($password, $user->getPassword())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->SetBool(self::IS_LOGGED_IN, true);
|
||||
$this->SetInt(self::USER_ID, $user->getUserId());
|
||||
$this->SetString(self::USERNAME, $user->getUsername());
|
||||
$this->SetString(self::EMAIL, $user->getEmail());
|
||||
$this->SetString(self::JABBER_ADDRESS, $user->getJabberAddress());
|
||||
$this->SetBool(self::IS_ADMIN, $user->isAdmin());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function HasValue(string $key): bool
|
||||
{
|
||||
return self::HasSession() && isset($_SESSION[$key]);
|
||||
}
|
||||
|
||||
public function SetBool(string $key, bool $value): void
|
||||
{
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
|
||||
public function SetString(string $key, string $value): void
|
||||
{
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
|
||||
public function SetInt(string $key, int $value): void
|
||||
{
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
|
||||
public function IsLoggedIn(): bool
|
||||
{
|
||||
return self::HasSession() && $this->GetBool(self::IS_LOGGED_IN);
|
||||
}
|
||||
|
||||
public function GetInt(string $key): ?int
|
||||
{
|
||||
return $this->HasValue($key) ? (int)$_SESSION[$key] : null;
|
||||
}
|
||||
|
||||
public function GetString(string $key): ?string
|
||||
{
|
||||
return $this->HasValue($key) ? (string)$_SESSION[$key] : null;
|
||||
}
|
||||
|
||||
public function GetBool(string $key): ?bool
|
||||
{
|
||||
return $this->HasValue($key) ? (bool)$_SESSION[$key] : null;
|
||||
}
|
||||
|
||||
public function getUserId(): ?int
|
||||
{
|
||||
return $this->GetInt(self::USER_ID);
|
||||
}
|
||||
|
||||
public function isAdmin(): bool
|
||||
{
|
||||
return $this->GetBool(self::IS_ADMIN);
|
||||
}
|
||||
|
||||
public static function HasSession(): bool
|
||||
{
|
||||
return isset($_SESSION);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Basic object to load a mysql table inside an object.
|
||||
*/
|
||||
abstract class Table
|
||||
{
|
||||
public const TYPE_STRING = 1;
|
||||
public const TYPE_INTEGER = 2;
|
||||
public const TYPE_FLOAT = 3;
|
||||
public const TYPE_DATETIME = 4;
|
||||
public const TYPE_BOOL = 5;
|
||||
|
||||
protected const VALUE = 'value';
|
||||
protected const TYPE = 'type';
|
||||
|
||||
protected ?DatabaseInterface $database;
|
||||
protected string $tableName;
|
||||
protected array $fields;
|
||||
protected string $primaryKey;
|
||||
protected bool $isPrimKeyManual = false;
|
||||
|
||||
public function __construct(string $tableName, $id, ?DatabaseInterface & $database)
|
||||
{
|
||||
$this->tableName = $tableName;
|
||||
$this->fields = [];
|
||||
|
||||
$this->database = $database;
|
||||
|
||||
$this->database->Query(sprintf('DESCRIBE %s', $tableName));
|
||||
|
||||
$result = $this->database->getResult();
|
||||
|
||||
foreach ($result as $field) {
|
||||
$sqlType = substr_count(
|
||||
$field['Type'], '(') === 0 ? $field['Type'] : strstr($field['Type'],
|
||||
'(',
|
||||
true
|
||||
);
|
||||
|
||||
switch ($sqlType) {
|
||||
case 'varchar':
|
||||
case 'char':
|
||||
case 'text':
|
||||
case 'longtext':
|
||||
case 'mediumtext':
|
||||
case 'tinytext':
|
||||
$type = self::TYPE_STRING;
|
||||
break;
|
||||
case 'int':
|
||||
case 'smallint':
|
||||
case 'mediumint':
|
||||
case 'bigint':
|
||||
$type = self::TYPE_INTEGER;
|
||||
break;
|
||||
case 'float':
|
||||
case 'decimal':
|
||||
case 'double':
|
||||
case 'real':
|
||||
$type = self::TYPE_FLOAT;
|
||||
break;
|
||||
case 'datetime':
|
||||
case 'date':
|
||||
$type = self::TYPE_DATETIME;
|
||||
break;
|
||||
case 'tinyint':
|
||||
$type = self::TYPE_BOOL;
|
||||
break;
|
||||
default:
|
||||
throw new DatabaseException(
|
||||
sprintf('Type %s of field %s couldn\'t be handled', $sqlType, $field['Field']),
|
||||
ServerStatus::INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
$this->addField($field['Field'], $type);
|
||||
|
||||
if ($field['Key'] === 'PRI') {
|
||||
$this->primaryKey = $field['Field'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->isPrimKeyManual && $id !== null) {
|
||||
$this->loadById($id);
|
||||
}
|
||||
}
|
||||
|
||||
public function getPrimaryKey()
|
||||
{
|
||||
if ($this->primaryKey === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getField($this->primaryKey);
|
||||
}
|
||||
|
||||
protected function addField(string $name, int $type): void
|
||||
{
|
||||
if (!self::IsValidType($type)) {
|
||||
throw new DatabaseException(
|
||||
sprintf('Field %s has invalid type of %s!', $name, $type),
|
||||
ServerStatus::INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
$this->fields[$name] = [self::VALUE => null, self::TYPE => $type];
|
||||
}
|
||||
|
||||
protected function loadById($id): void
|
||||
{
|
||||
$this->database->Query(
|
||||
sprintf('SELECT * FROM %s WHERE %s = :id', $this->tableName, $this->primaryKey),
|
||||
['id' => $id]
|
||||
);
|
||||
|
||||
$result = $this->database->getResult();
|
||||
|
||||
if (count($result) === 0) {
|
||||
throw new DatabaseException(
|
||||
sprintf('No %s with id %d found!', $this->tableName, $id),
|
||||
ServerStatus::BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($result[0] as $field => $value) {
|
||||
$this->setField($field, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function Flush(): void
|
||||
{
|
||||
$this->database->Delete($this->tableName, []);
|
||||
}
|
||||
|
||||
public function Delete(): void
|
||||
{
|
||||
$this->database->Delete($this->tableName, [$this->primaryKey => $this->getPrimaryKey()]);
|
||||
|
||||
foreach ($this->GetAllFieldNames() as $field) {
|
||||
$this->fields[$field][self::VALUE] = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getField(string $name)
|
||||
{
|
||||
if (!array_key_exists($name, $this->fields)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->fields[$name][self::VALUE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value for the given field inside the database.
|
||||
*/
|
||||
protected function setField(string $name, $value): void
|
||||
{
|
||||
if (!$this->HasField($name)) {
|
||||
throw new DatabaseException(
|
||||
sprintf('Field %s doesn\'t exist!', $name),
|
||||
ServerStatus::INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
$this->fields[$name][self::VALUE] = null;
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($this->fields[$name][self::TYPE]) {
|
||||
case self::TYPE_STRING:
|
||||
$this->fields[$name][self::VALUE] = (string)$value;
|
||||
return;
|
||||
case self::TYPE_INTEGER:
|
||||
$this->fields[$name][self::VALUE] = (int)$value;
|
||||
return;
|
||||
case self::TYPE_FLOAT:
|
||||
$this->fields[$name][self::VALUE] = (float)$value;
|
||||
return;
|
||||
case self::TYPE_DATETIME:
|
||||
try {
|
||||
$this->fields[$name][self::VALUE] = new DateTime((string)$value);
|
||||
} catch (Throwable $e) {
|
||||
throw new DatabaseException(
|
||||
$e->getMessage(),
|
||||
ServerStatus::INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
return;
|
||||
case self::TYPE_BOOL:
|
||||
$this->fields[$name][self::VALUE] = (bool)$value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the table has the given column.
|
||||
*/
|
||||
public function HasField(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, $this->fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the whole object into the database.
|
||||
*/
|
||||
public function Save(): void
|
||||
{
|
||||
$fields = [];
|
||||
|
||||
foreach ($this->GetAllFieldNames() as $fieldName) {
|
||||
$field = $this->getField($fieldName);
|
||||
|
||||
if ($field instanceof DateTime) {
|
||||
$fields[$fieldName] = $field->format('Y-m-d H:i:s');
|
||||
} else if (is_bool($field)) {
|
||||
$fields[$fieldName] = (int)$field;
|
||||
} else {
|
||||
$fields[$fieldName] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isPrimKeyManual) {
|
||||
$this->saveWithManualId($fields);
|
||||
} else {
|
||||
$this->saveWithPrimaryKey($fields);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function GetAllFieldNames(): array
|
||||
{
|
||||
$fieldNames = [];
|
||||
|
||||
foreach ($this->fields as $name => $field) {
|
||||
$fieldNames[] = $name;
|
||||
}
|
||||
|
||||
return $fieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the index is a valid backend type.
|
||||
*/
|
||||
public static function IsValidType(int $type): bool
|
||||
{
|
||||
$validTypes = [
|
||||
self::TYPE_STRING,
|
||||
self::TYPE_INTEGER,
|
||||
self::TYPE_FLOAT,
|
||||
self::TYPE_DATETIME,
|
||||
self::TYPE_BOOL,
|
||||
];
|
||||
|
||||
return in_array($type, $validTypes);
|
||||
}
|
||||
|
||||
protected function saveWithManualId(array $fields): void
|
||||
{
|
||||
if ($this->getField($this->primaryKey) === null) {
|
||||
throw new DatabaseException(
|
||||
'Manual primary key must not be null!',
|
||||
ServerStatus::INTERNAL_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
$hasKey = (bool)$this->database->Count(
|
||||
$this->tableName,
|
||||
[$this->primaryKey => $this->getField($this->primaryKey)]
|
||||
);
|
||||
|
||||
if ($hasKey) {
|
||||
$this->database->Update(
|
||||
$this->tableName, $fields, [$this->primaryKey => $this->getField($this->primaryKey)]
|
||||
);
|
||||
} else {
|
||||
$this->database->Insert($this->tableName, $fields);
|
||||
}
|
||||
}
|
||||
|
||||
protected function saveWithPrimaryKey(array $fields): void
|
||||
{
|
||||
if ($this->getField($this->primaryKey) !== null) {
|
||||
$this->database->Update(
|
||||
$this->tableName, $fields, [$this->primaryKey => $this->getField($this->primaryKey)]
|
||||
);
|
||||
} else {
|
||||
$this->setField($this->primaryKey, $this->database->Insert($this->tableName, $fields));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class Fingerprint extends MySqlTable implements JsonSerializable
|
||||
{
|
||||
public const FIELD_ID = 'FingerprintId';
|
||||
public const FIELD_FINGERPRINT = 'Fingerprint';
|
||||
public const FIELD_USER = 'UserId';
|
||||
|
||||
public function __construct($id = null, DatabaseInterface &$database = null)
|
||||
{
|
||||
parent::__construct(self::class, $id, $database);
|
||||
}
|
||||
|
||||
public function getFingerprintId(): ?int
|
||||
{
|
||||
if ($this->getPrimaryKey() === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int)$this->getPrimaryKey();
|
||||
}
|
||||
|
||||
public function getFingerprint(): string
|
||||
{
|
||||
return $this->getField(self::FIELD_FINGERPRINT);
|
||||
}
|
||||
|
||||
public function getUserId(): int
|
||||
{
|
||||
return $this->getField(self::FIELD_USER);
|
||||
}
|
||||
|
||||
public function setFingerprint(string $fingerprint): void
|
||||
{
|
||||
$this->setField(self::FIELD_FINGERPRINT, $fingerprint);
|
||||
}
|
||||
|
||||
public function setUserId(int $userId): void
|
||||
{
|
||||
$this->setField(self::FIELD_USER, $userId);
|
||||
}
|
||||
|
||||
public function isSharedWith(int $userId): bool
|
||||
{
|
||||
return (bool)$this->database->Count(
|
||||
Sharing::class,
|
||||
[Sharing::FIELD_USER => $this->getUserId(), Sharing::FIELD_USER_SHARED => $userId]
|
||||
);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'fingerprintId' => $this->getFingerprintId(),
|
||||
'fingerprint' => $this->getFingerprint(),
|
||||
'userId' => $this->getUserId()
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class FingerprintCollection implements Iterator, JsonSerializable
|
||||
{
|
||||
private int $position = 0;
|
||||
|
||||
/** @var Fingerprint[] */
|
||||
private array $fingerprints = [];
|
||||
|
||||
public function add(Fingerprint $fingerprint): void
|
||||
{
|
||||
$this->fingerprints[] = $fingerprint;
|
||||
}
|
||||
|
||||
public function current(): Fingerprint
|
||||
{
|
||||
return $this->fingerprints[$this->position];
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
$this->position++;
|
||||
}
|
||||
|
||||
public function key(): int
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function valid(): bool
|
||||
{
|
||||
return isset($this->fingerprints[$this->position]);
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->position = 0;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$fingerprints = [];
|
||||
|
||||
foreach ($this->fingerprints as $fingerprint) {
|
||||
$fingerprints[] = $fingerprint->jsonSerialize();
|
||||
}
|
||||
|
||||
return $fingerprints;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class Sharing extends MySqlTable implements JsonSerializable
|
||||
{
|
||||
public const FIELD_USER = 'User';
|
||||
public const FIELD_USER_SHARED = 'UserShared';
|
||||
|
||||
public function __construct($id = null, DatabaseInterface &$database = null)
|
||||
{
|
||||
parent::__construct(self::class, $id, $database);
|
||||
}
|
||||
|
||||
public function getSharingId(): ?int
|
||||
{
|
||||
if ($this->getPrimaryKey() === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int)$this->getPrimaryKey();
|
||||
}
|
||||
|
||||
public function getUserId(): int
|
||||
{
|
||||
return $this->getField(self::FIELD_USER);
|
||||
}
|
||||
|
||||
public function getUserShared(): int
|
||||
{
|
||||
return $this->getField(self::FIELD_USER_SHARED);
|
||||
}
|
||||
|
||||
public function setUserId(int $userId): void
|
||||
{
|
||||
$this->setField(self::FIELD_USER, $userId);
|
||||
}
|
||||
|
||||
public function setUserShared(int $userShared): void
|
||||
{
|
||||
$this->setField(self::FIELD_USER_SHARED, $userShared);
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return [
|
||||
'sharingId' => $this->getSharingId(),
|
||||
'userId' => $this->getUserId(),
|
||||
'userSharedId' => $this->getUserShared(),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class User extends MySqlTable implements JsonSerializable
|
||||
{
|
||||
public const FIELD_ID = 'UserId';
|
||||
public const FIELD_USERNAME = 'Username';
|
||||
public const FIELD_PASSWORD = 'Password';
|
||||
public const FIELD_EMAIL = 'Email';
|
||||
public const FIELD_JABBER_ADDRESS = 'JabberAddress';
|
||||
public const FIELD_ADMIN = 'IsAdmin';
|
||||
|
||||
public function __construct($id = null, DatabaseInterface &$database = null)
|
||||
{
|
||||
parent::__construct(self::class, $id, $database);
|
||||
}
|
||||
|
||||
public function getUserId(): ?int
|
||||
{
|
||||
if ($this->getPrimaryKey() === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int)$this->getPrimaryKey();
|
||||
}
|
||||
|
||||
public function getUsername(): string
|
||||
{
|
||||
return $this->getField(self::FIELD_USERNAME);
|
||||
}
|
||||
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->getField(self::FIELD_PASSWORD);
|
||||
}
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->getField(self::FIELD_EMAIL);
|
||||
}
|
||||
|
||||
public function getJabberAddress(): string
|
||||
{
|
||||
return $this->getField(self::FIELD_JABBER_ADDRESS);
|
||||
}
|
||||
|
||||
public function isAdmin(): bool
|
||||
{
|
||||
return $this->getField(self::FIELD_ADMIN);
|
||||
}
|
||||
|
||||
public function setUsername(string $username): void
|
||||
{
|
||||
$this->setField(self::FIELD_USERNAME, $username);
|
||||
}
|
||||
|
||||
public function setPassword(string $password): void
|
||||
{
|
||||
$this->setField(self::FIELD_PASSWORD, $password);
|
||||
}
|
||||
|
||||
public function setEmail(string $email): void
|
||||
{
|
||||
$this->setField(self::FIELD_EMAIL, $email);
|
||||
}
|
||||
|
||||
public function setJabberAddress(string $jabberAddress): void
|
||||
{
|
||||
$this->setField(self::FIELD_JABBER_ADDRESS, $jabberAddress);
|
||||
}
|
||||
|
||||
public function setAdmin(bool $isAdmin): void
|
||||
{
|
||||
$this->setField(self::FIELD_ADMIN, $isAdmin);
|
||||
}
|
||||
|
||||
public static function getFromUsername(string $username, DatabaseInterface &$database = null): self
|
||||
{
|
||||
$databaseGiven = true;
|
||||
|
||||
if ($database === null) {
|
||||
$database = new MySqlDatabase();
|
||||
$databaseGiven = false;
|
||||
}
|
||||
|
||||
if ($database->Count(self::class, [self::FIELD_USERNAME => $username]) === 0) {
|
||||
throw new UserException(sprintf('No user with name %s found!', $username));
|
||||
}
|
||||
|
||||
$id = $database->Select(self::class, [self::FIELD_ID], [self::FIELD_USERNAME => $username])[0][self::FIELD_ID];
|
||||
|
||||
$user = $databaseGiven ? new User((int)$id, $database) : new User((int)$id);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public static function getFromEmail(string $email, DatabaseInterface &$database = null): self
|
||||
{
|
||||
$databaseGiven = true;
|
||||
|
||||
if ($database === null) {
|
||||
$database = new MySqlDatabase();
|
||||
$databaseGiven = false;
|
||||
}
|
||||
|
||||
if ($database->Count(self::class) === 0) {
|
||||
throw new UserException(sprintf('No user with email %s found!', $email));
|
||||
}
|
||||
|
||||
$id = $database->Select(self::class, [self::FIELD_ID], [self::FIELD_EMAIL => $email])[0][self::FIELD_ID];
|
||||
|
||||
$user = $databaseGiven ? new User((int)$id, $database) : new User((int)$id);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function getFingerprintIds(): array
|
||||
{
|
||||
$result = $this->database->Select(
|
||||
Fingerprint::class,
|
||||
[Fingerprint::FIELD_ID],
|
||||
[Fingerprint::FIELD_USER => $this->getUserId()]
|
||||
);
|
||||
|
||||
$ids = [];
|
||||
|
||||
foreach ($result as $record) {
|
||||
$ids[] = (int)$record[Fingerprint::FIELD_ID];
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
public function isSharingWith(int $userId): bool
|
||||
{
|
||||
return (bool)$this->database->Count(
|
||||
Sharing::class,
|
||||
[Sharing::FIELD_USER => $this->getUserId(), Sharing::FIELD_USER_SHARED => $userId]
|
||||
);
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return [
|
||||
'userId' => $this->getUserId(),
|
||||
'username' => $this->getUsername(),
|
||||
'jabberAddress' => $this->getJabberAddress(),
|
||||
'isAdmin' => $this->isAdmin(),
|
||||
'fingerprintIds' => $this->getFingerprintIds()
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class DatabaseException extends Exception
|
||||
{
|
||||
public const CONNECTION_FAILED = 1;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
require 'backend/classes/core/Autoloader.php';
|
||||
|
||||
Autoloader::BuildCache();
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
require 'backend/classes/core/Autoloader.php';
|
||||
|
||||
$autoloader = new Autoloader();
|
||||
|
||||
echo 'Checking for missing qr codes...' . PHP_EOL;
|
||||
|
||||
$db = new MySqlDatabase();
|
||||
|
||||
$countForMissing = 0;
|
||||
|
||||
foreach ($db->Select(Fingerprint::class, [Fingerprint::FIELD_ID]) as $record) {
|
||||
$fingerprint = new Fingerprint((int)$record[Fingerprint::FIELD_ID], $db);
|
||||
|
||||
$qrCode = new QrCode($fingerprint->getFingerprintId(), $fingerprint->getFingerprint());
|
||||
|
||||
if (!$qrCode->hasFile()) {
|
||||
$countForMissing++;
|
||||
|
||||
$qrCode->generate();
|
||||
$qrCode->save();
|
||||
|
||||
echo sprintf("\t=> Missing QR code for fingerprint %d generated.\n", $fingerprint->getFingerprintId());
|
||||
}
|
||||
}
|
||||
|
||||
echo $countForMissing === 0
|
||||
? 'No missing QR codes found.' . PHP_EOL
|
||||
: sprintf('%d missing QR code(s) fixed.', $countForMissing) . PHP_EOL;
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
function getUserInput(string $label): string
|
||||
{
|
||||
echo $label;
|
||||
|
||||
return str_replace("\n", '', fgets(STDIN));
|
||||
}
|
||||
|
||||
const TEMPLATE_SETTINGS = '<?php
|
||||
|
||||
/*
|
||||
* This file was auto generated on :DATETIME
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class Setting
|
||||
{
|
||||
public const MYSQL_HOST = \':HOST\';
|
||||
public const MYSQL_USER = \':USER\';
|
||||
public const MYSQL_PASSWORD = \':PASSWORD\';
|
||||
public const MYSQL_DATABASE = \':DATABASE\';
|
||||
|
||||
public const PATH_ROOT = \':ROOT_PATH/\';
|
||||
public const PATH_QR_CODES = self::PATH_ROOT . \'backend/qr/\';
|
||||
public const PATH_TMP = self::PATH_ROOT . \'backend/tmp/\';
|
||||
}
|
||||
';
|
||||
|
||||
$hostname = getUserInput('MySQL host address (default "localhost"): ');
|
||||
$username = getUserInput('MySQL user: ');
|
||||
$password = getUserInput('MySQL password (shown!): ');
|
||||
$database = getUserInput('MySQL database: ');
|
||||
|
||||
$settings = str_replace(
|
||||
[':HOST', ':USER', ':PASSWORD', ':DATABASE', ':ROOT_PATH', ':DATETIME'],
|
||||
[$hostname, $username, $password, $database, getcwd(), (new DateTime())->format('Y-m-d H:i:s')],
|
||||
TEMPLATE_SETTINGS
|
||||
);
|
||||
|
||||
$file = fopen(getcwd() . '/backend/classes/Setting.php', 'w');
|
||||
fwrite($file, $settings);
|
||||
fclose($file);
|
||||
|
||||
require 'backend/classes/core/Autoloader.php';
|
||||
|
||||
Autoloader::BuildCache();
|
||||
|
||||
$autoloader = new Autoloader();
|
||||
|
||||
$file = fopen('backend/scripts/setup.sql', 'r');
|
||||
$setupSql = fread($file, filesize('backend/scripts/setup.sql'));
|
||||
fclose($file);
|
||||
|
||||
$db = new MySqlDatabase($hostname, $username, $password, $database);
|
||||
$db->Query($setupSql);
|
||||
|
||||
echo 'Ringfinger setup has successfully finished.' . PHP_EOL;
|
|
@ -0,0 +1,39 @@
|
|||
START TRANSACTION;
|
||||
|
||||
USE ringfinger;
|
||||
|
||||
CREATE TABLE User (
|
||||
UserId int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
Username varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
Password varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
Email varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
JabberAddress varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
IsAdmin BOOL NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (UserId),
|
||||
UNIQUE KEY Username (Username),
|
||||
UNIQUE KEY Email (Email),
|
||||
UNIQUE KEY JabberAddress (JabberAddress)
|
||||
);
|
||||
|
||||
CREATE TABLE Fingerprint (
|
||||
FingerprintId int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
Fingerprint varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
UserId int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (FingerprintId),
|
||||
UNIQUE KEY Fingerprint (Fingerprint),
|
||||
KEY User (UserId),
|
||||
CONSTRAINT User FOREIGN KEY (UserId) REFERENCES User (UserId) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE Sharing (
|
||||
SharingId int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
User int(10) unsigned NOT NULL,
|
||||
UserShared int(10) unsigned NOT NULL,
|
||||
PRIMARY KEY (SharingId),
|
||||
UNIQUE KEY User (User, UserShared),
|
||||
KEY UserSharedId (UserShared),
|
||||
CONSTRAINT UserId FOREIGN KEY (User) REFERENCES User (UserId) ON DELETE CASCADE,
|
||||
CONSTRAINT UserSharedId FOREIGN KEY (UserShared) REFERENCES User (UserId) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
COMMIT;
|
|
@ -1,13 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class ApiBadRequestResponse extends ApiResponse
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setParameter('success', false);
|
||||
$this->setStatus(self::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class ApiJsonResponse extends ApiResponse
|
||||
{
|
||||
protected string $mimeType = MimeType::JSON;
|
||||
|
||||
public function __construct(int $status = ServerStatus::OK)
|
||||
{
|
||||
parent::__construct($status);
|
||||
|
||||
$this->setParameter('success', true);
|
||||
}
|
||||
|
||||
public function setResult(JsonSerializable $result): void
|
||||
{
|
||||
$this->setParameter('result', $result->jsonSerialize());
|
||||
}
|
||||
|
||||
public function respond(): void
|
||||
{
|
||||
parent::respond();
|
||||
|
||||
echo json_encode($this->parameters);
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class ApiResponse implements JsonSerializable
|
||||
{
|
||||
public const STATUS_OK = 200;
|
||||
public const STATUS_FORBIDDEN = 403;
|
||||
public const STATUS_UNAUTHORIZED = 401;
|
||||
public const STATUS_BAD_REQUEST = 400;
|
||||
public const STATUS_NOT_FOUND = 404;
|
||||
public const STATUS_SERVER_ERROR = 500;
|
||||
|
||||
public const MIME_TYPE_PLAINTEXT = 'text/plain';
|
||||
public const MIME_TYPE_JSON = 'application/json';
|
||||
public const MIME_TYPE_SVG = 'image/svg+xml';
|
||||
|
||||
protected int $status = ServerStatus::OK;
|
||||
protected string $mimeType = MimeType::PLAINTEXT;
|
||||
protected array $parameters = [];
|
||||
|
||||
public function __construct(int $status = ServerStatus::OK)
|
||||
{
|
||||
$this->setStatus($status);
|
||||
}
|
||||
|
||||
public function setParameter(string $key, $value): void
|
||||
{
|
||||
$this->parameters[$key] = $value;
|
||||
}
|
||||
|
||||
public function setStatus(int $status): void
|
||||
{
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
public function setMessage(string $message): void
|
||||
{
|
||||
$this->setParameter('message', $message);
|
||||
}
|
||||
|
||||
public function setMimeType(string $mimeType): void
|
||||
{
|
||||
$this->mimeType = $mimeType;
|
||||
}
|
||||
|
||||
public function setBody(JsonSerializable $data): void
|
||||
{
|
||||
$this->parameters = $data->jsonSerialize();
|
||||
}
|
||||
|
||||
public function SetMessageIdNotFound(string $instanceName): void
|
||||
{
|
||||
$this->setMessage(sprintf('Die für %s angeforderte ID existiert nicht!', $instanceName));
|
||||
}
|
||||
|
||||
public function getStatus(): int
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function getMimeType(): string
|
||||
{
|
||||
return $this->mimeType;
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
public function respond(): void
|
||||
{
|
||||
http_response_code($this->status);
|
||||
header('Content-Type: ' . $this->mimeType);
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class ApiSuccessResponse extends ApiResponse
|
||||
{
|
||||
public function __construct(bool $success = true)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setParameter('success', $success);
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class ApiSvgResponse extends ApiResponse
|
||||
{
|
||||
private string $content;
|
||||
protected string $mimeType = MimeType::SVG;
|
||||
|
||||
public function __construct(int $status = ServerStatus::OK)
|
||||
{
|
||||
parent::__construct($status);
|
||||
}
|
||||
|
||||
public function setContent(string $content): void
|
||||
{
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
public function respond(): void
|
||||
{
|
||||
parent::respond();
|
||||
header('Content-Length: ' . strlen($this->content));
|
||||
echo $this->content;
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class ApiUnauthorizedResponse extends ApiResponse
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->setStatus(self::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class FingerprintGetController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/fingerprint/{fingerprintId}';
|
||||
|
||||
private int $fingerprintId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
|
||||
$this->fingerprintId = (int)$this->getUrlParamInt('fingerprintId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$fingerprint = new Fingerprint($this->fingerprintId);
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
$this->response->setResult($fingerprint->jsonSerialize());
|
||||
} catch (Throwable $e) {
|
||||
$this->response = new ApiJsonResponse(ApiResponse::STATUS_NOT_FOUND);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage(sprintf('No fingerprint with id %d found!', $this->fingerprintId));
|
||||
$this->response->setMimeType(ApiResponse::MIME_TYPE_JSON);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class FingerprintPostController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/fingerprint';
|
||||
protected array $mandatoryAttributes = [
|
||||
'fingerprint',
|
||||
'userId',
|
||||
];
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
parent::handle();
|
||||
|
||||
$db = new MySqlDatabase();
|
||||
$json = json_decode($this->requestBody);
|
||||
$fingerprint = new Fingerprint(null, $db);
|
||||
|
||||
try {
|
||||
$fingerprint->setFingerprint($json->fingerprint);
|
||||
$fingerprint->setUserId($json->userId);
|
||||
|
||||
if (!$db->hasTransaction()) {
|
||||
$db->startTransaction();
|
||||
}
|
||||
|
||||
$fingerprint->Save();
|
||||
|
||||
$qrCode = new QrCode($fingerprint->getFingerprintId(), $fingerprint->getFingerprint());
|
||||
$qrCode->generate();
|
||||
$qrCode->save();
|
||||
|
||||
$db->commit();
|
||||
|
||||
$this->response->setParameter('fingerprintId', $fingerprint->getFingerprintId());
|
||||
} catch (QrCodeException $e) {
|
||||
$db->rollback();
|
||||
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setStatus(ServerStatus::INTERNAL_ERROR);
|
||||
$this->response->setMessage('An error occured during qr code creation!');
|
||||
} catch (Throwable $e) {
|
||||
$db->rollback();
|
||||
$this->catchDatabaseException($e->getMessage(), $json);
|
||||
}
|
||||
}
|
||||
|
||||
private function catchDatabaseException(string $message, object $json): void
|
||||
{
|
||||
$this->response->setParameter('success', false);
|
||||
|
||||
if (substr_count($message, 'foreign key constraint fails') > 0) {
|
||||
$this->response->setMessage(sprintf('User with id %d doesn\'t exist!', $json->userId));
|
||||
$this->response->setStatus(ServerStatus::NOT_FOUND);
|
||||
} elseif (substr_count($message, 'Duplicate entry') > 0) {
|
||||
$this->response->setMessage(sprintf('Fingerprint %s already exists!', $json->fingerprint));
|
||||
$this->response->setStatus(ServerStatus::BAD_REQUEST);
|
||||
} else {
|
||||
$this->response->setMessage($message);
|
||||
$this->response->setStatus(ServerStatus::INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class QrCodeGetController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/fingerprint/{fingerprintId}/qr';
|
||||
|
||||
private int $fingerprintId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
|
||||
$this->fingerprintId = (int)$this->getUrlParamInt('fingerprintId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$filename = Setting::PATH_QR_CODES . (string)$this->fingerprintId . '.svg';
|
||||
|
||||
if (!is_file($filename)) {
|
||||
$this->response = new ApiJsonResponse();
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage('No QR code for fingerprint id %d found!');
|
||||
$this->response->setMimeType(ApiResponse::MIME_TYPE_JSON);
|
||||
}
|
||||
|
||||
$this->response = new ApiSvgResponse();
|
||||
|
||||
$file = fopen($filename, 'r');
|
||||
$this->response->setContent(fread($file, filesize($filename)));
|
||||
fclose($file);
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserGetController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user/{userId}';
|
||||
|
||||
private int $userId;
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
parent::__construct($url);
|
||||
|
||||
$this->userId = (int)$this->getUrlParamInt('userId');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$user = new User($this->userId);
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
$this->response->setResult($user);
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserLoginPutController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user/login';
|
||||
protected array $mandatoryAttributes = [
|
||||
'username',
|
||||
'password',
|
||||
];
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
parent::handle();
|
||||
|
||||
if ($this->response->getStatus() !== ServerStatus::OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
$json = json_decode($this->requestBody);
|
||||
|
||||
$session = new Session();
|
||||
|
||||
if ($session->IsLoggedIn()) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage('You are already logged in!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$session->Login($json->username, $json->password)) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::UNAUTHORIZED);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage('Login failed!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UserLogoutPutController extends AbstractController
|
||||
{
|
||||
protected string $route = '/api/v1/user/logout';
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
parent::handle();
|
||||
|
||||
$session = new Session();
|
||||
|
||||
if (!$session->IsLoggedIn()) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage('You were not logged in!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$session->Destroy();
|
||||
|
||||
$this->response = new ApiJsonResponse();
|
||||
$this->response->setParameter('success', true);
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
abstract class AbstractController
|
||||
{
|
||||
protected string $route;
|
||||
protected ApiResponse $response;
|
||||
|
||||
protected string $requestUrl;
|
||||
protected ?string $requestBody = null;
|
||||
protected ?string $contentType = null;
|
||||
protected array $mandatoryAttributes = [];
|
||||
|
||||
public function __construct(string $url)
|
||||
{
|
||||
$this->requestUrl = $url;
|
||||
$this->response = new ApiResponse();
|
||||
}
|
||||
|
||||
public function setRequestBody(string $contentType, string $content): void
|
||||
{
|
||||
$this->requestBody = $content;
|
||||
$this->contentType = $contentType;
|
||||
}
|
||||
|
||||
public function getResponse(): ApiResponse
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if (!$this->validateJsonBody()) {
|
||||
$this->response = new ApiJsonResponse(ServerStatus::BAD_REQUEST);
|
||||
$this->response->setParameter('success', false);
|
||||
$this->response->setMessage('The request body has not the required json attributes!');
|
||||
}
|
||||
}
|
||||
|
||||
protected function getUrlParam(string $name): ?string
|
||||
{
|
||||
foreach (explode('/', $this->route) as $index => $fragment) {
|
||||
if ($fragment === '{' . $name . '}') {
|
||||
return explode('/', $this->requestUrl)[$index];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getUrlParamInt(string $name): ?int
|
||||
{
|
||||
$param = $this->getUrlParam($name);
|
||||
|
||||
return $param !== null ? (int)$param : null;
|
||||
}
|
||||
|
||||
protected function validateJsonBody(): bool
|
||||
{
|
||||
if (count($this->mandatoryAttributes) === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->contentType === MimeType::JSON && $this->requestBody === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$json = json_decode($this->requestBody);
|
||||
|
||||
foreach ($this->mandatoryAttributes as $attribute) {
|
||||
if (!isset($json->{$attribute})) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,245 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class Autoloader
|
||||
{
|
||||
private const PATH_CLASSES = 'data/classes';
|
||||
private const PATH_CONTROLLERS = self::PATH_CLASSES . '/controller';
|
||||
private const PATH_CACHE = 'data/cache/';
|
||||
|
||||
public function __construct(string $cachePath = self::PATH_CACHE)
|
||||
{
|
||||
if ($cachePath !== self::PATH_CACHE) {
|
||||
$cachePath = substr($cachePath, -1) === '/' ? $cachePath : $cachePath . '/';
|
||||
}
|
||||
|
||||
$routesFound = @include($cachePath . 'routes.php');
|
||||
$classesFound = @include($cachePath . 'classes.php');
|
||||
|
||||
if (!$routesFound || !$classesFound) {
|
||||
throw new Exception(
|
||||
sprintf(
|
||||
'Autoloader cache not found! Please generate it with %s::BuildCache() at first!',
|
||||
self::class
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
spl_autoload_register(
|
||||
function (string $className) {
|
||||
if (!$this->loadClass($className)) {
|
||||
throw new Exception(sprintf('Class %s couldn\'t be loaded!', $className));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static function BuildCache(): void
|
||||
{
|
||||
self::BuildClassCache();
|
||||
self::BuildRouteCache();
|
||||
}
|
||||
|
||||
public static function BuildClassCache(): void
|
||||
{
|
||||
$classesResult = self::scanForClasses();
|
||||
|
||||
$cacheContent = '';
|
||||
|
||||
foreach ($classesResult as $className => $path) {
|
||||
$cacheContent .= sprintf("\t\t'%s' => '%s',\n", $className, $path);
|
||||
}
|
||||
|
||||
$cacheContent .= "\t]\n);";
|
||||
|
||||
self::buildCacheFile($cacheContent, 'classes');
|
||||
}
|
||||
|
||||
private function loadClass(string $className): bool
|
||||
{
|
||||
if (!isset(CLASSES[$className]) || !@include(CLASSES[$className])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function BuildRouteCache(): void
|
||||
{
|
||||
$controllersResult = self::scanForControllers();
|
||||
$controllerMethods = [
|
||||
'GET' => [],
|
||||
'POST' => [],
|
||||
'PUT' => [],
|
||||
];
|
||||
|
||||
foreach ($controllersResult as $className => $path) {
|
||||
$file = fopen($path, 'r');
|
||||
$content = fread($file, filesize($path));
|
||||
fclose($file);
|
||||
|
||||
preg_match_all('/(?<=private )\w+ \$\w+(?=;)/', $content, $matches);
|
||||
|
||||
$params = [];
|
||||
|
||||
foreach ($matches[0] as $match) {
|
||||
$parts = explode(' ', $match);
|
||||
$params[] = [
|
||||
'type' => $parts[0],
|
||||
'name' => $parts[1],
|
||||
];
|
||||
}
|
||||
|
||||
preg_match('/(?<=protected string \$route = \').*(?=\';)/', $content, $matches);
|
||||
$route = $matches[0];
|
||||
|
||||
preg_match('/[A-Z][a-z]+(?=Controller)/', $className, $matches);
|
||||
$method = strtoupper($matches[0]);
|
||||
|
||||
$controllerMethods[$method][$route] = [
|
||||
'name' => $className,
|
||||
'params' => $params,
|
||||
];
|
||||
}
|
||||
|
||||
$cacheContent = '';
|
||||
|
||||
foreach ($controllerMethods as $method => $controllers) {
|
||||
$cacheContent .= self::createRoutesForMethod($method, $controllers);
|
||||
}
|
||||
|
||||
$cacheContent .= "\t]\n);";
|
||||
|
||||
self::buildCacheFile($cacheContent, 'routes');
|
||||
}
|
||||
|
||||
private static function createRoutesForMethod(string $method, array $routes): string
|
||||
{
|
||||
krsort($routes);
|
||||
$stringRoutes = '';
|
||||
|
||||
foreach ($routes as $route => $params) {
|
||||
$stringRoutes .= sprintf(
|
||||
"'%s' => [
|
||||
'controller' => %s::class,
|
||||
'params' => [
|
||||
%s
|
||||
],
|
||||
],
|
||||
",
|
||||
$route,
|
||||
$params['name'],
|
||||
self::createRouteParams($params['params'])
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
"
|
||||
'%s' => [
|
||||
%s
|
||||
],
|
||||
",
|
||||
$method,
|
||||
$stringRoutes,
|
||||
);
|
||||
}
|
||||
|
||||
private static function createRouteParams(array $params): string
|
||||
{
|
||||
$string = '';
|
||||
|
||||
foreach ($params as $param) {
|
||||
$string .= sprintf(
|
||||
"
|
||||
'%s' => [
|
||||
'type' => '%s',
|
||||
],
|
||||
",
|
||||
str_replace('$', '', $param['name']),
|
||||
$param['type']
|
||||
);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
private static function reformatCacheFileContent(string $content): string
|
||||
{
|
||||
$depth = 0;
|
||||
$reformatted = '';
|
||||
$replace = '';
|
||||
|
||||
// Removing indents
|
||||
foreach (explode("\n", $content) as $line) {
|
||||
$trim = trim($line);
|
||||
|
||||
if ($trim !== '') {
|
||||
$replace .= $trim . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = 0; $i < strlen($replace); $i++) {
|
||||
if (in_array($replace[$i], [')', ']'])) {
|
||||
$depth--;
|
||||
}
|
||||
|
||||
if ($replace[$i - 1] === "\n") {
|
||||
$reformatted .= str_repeat("\t", $depth);
|
||||
}
|
||||
|
||||
$reformatted .= $replace[$i];
|
||||
|
||||
if (in_array($replace[$i], ['(', '['])) {
|
||||
$depth++;
|
||||
}
|
||||
}
|
||||
|
||||
return $reformatted;
|
||||
}
|
||||
|
||||
private static function buildCacheFile(string $content, string $cacheName): void
|
||||
{
|
||||
$cacheContent = sprintf(
|
||||
"<?php\n\n/*\n * This file was auto generated on %s\n */\n\ndefine(\n\t'%s',\n\t[\n",
|
||||
(new DateTime())->format('Y-m-d H:i:s'),
|
||||
strtoupper($cacheName)
|
||||
);
|
||||
|
||||
$cacheContent .= $content;
|
||||
|
||||
$file = fopen(getcwd() . '/' . self::PATH_CACHE . $cacheName . '.php', 'w');
|
||||
fwrite($file, self::reformatCacheFileContent($cacheContent));
|
||||
fclose($file);
|
||||
}
|
||||
|
||||
private static function scanForFiles(string $folder): array
|
||||
{
|
||||
$folder = substr($folder, -1) === '/' ? substr($folder, 0, -1) : $folder;
|
||||
$files = [];
|
||||
$handler = opendir($folder);
|
||||
|
||||
while ($file = readdir($handler)) {
|
||||
$path = $folder . '/' . $file;
|
||||
|
||||
if (is_dir($path) && $file !== '.' && $file !== '..') {
|
||||
$files = array_merge($files, self::scanForFiles($path));
|
||||
} elseif (is_file($path) && substr($path, -4) === '.php') {
|
||||
$className = substr($file, 0, -4);
|
||||
$files[$className] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
private static function scanForClasses(): array
|
||||
{
|
||||
return self::scanForFiles(getcwd() . '/' . self::PATH_CLASSES);
|
||||
}
|
||||
|
||||
private static function scanForControllers(): array
|
||||
{
|
||||
return self::scanForFiles(getcwd() . '/' . self::PATH_CONTROLLERS);
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
<?php
|
||||
|
||||
interface DatabaseInterface
|
||||
{
|
||||
public const ORDER_ASC = true;
|
||||
public const ORDER_DESC = false;
|
||||
|
||||
/**
|
||||
* Has to close the connection.
|
||||
*/
|
||||
public function __destruct();
|
||||
|
||||
/**
|
||||
* Sends an sql query to the database.
|
||||
*/
|
||||
public function Query(string $query, array $params = []): void;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getResult(): array;
|
||||
|
||||
/**
|
||||
* Selects data from a table.
|
||||
*/
|
||||
public function Select(
|
||||
string $tableName,
|
||||
array $fields = [],
|
||||
array $conditions = [],
|
||||
int $limit = 0,
|
||||
array $orderBy = [],
|
||||
bool $asc = true,
|
||||
int $offset = 0
|
||||
): array;
|
||||
|
||||
/**
|
||||
* Deletes rows from a table.
|
||||
*/
|
||||
public function Delete(string $table, array $conditions): void;
|
||||
|
||||
/**
|
||||
* Inserts a new row into the table.
|
||||
*/
|
||||
public function Insert(string $table, array $fields): ?int;
|
||||
|
||||
/**
|
||||
* Edits data inside a table.
|
||||
*/
|
||||
public function Update(string $table, array $fields, array $conditions): void;
|
||||
|
||||
/**
|
||||
* Returns the number of entries found.
|
||||
*/
|
||||
public function Count(string $table, array $conditions = []): int;
|
||||
|
||||
/*
|
||||
* Returns if there's an open transaction.
|
||||
*/
|
||||
public function hasTransaction(): bool;
|
||||
|
||||
/*
|
||||
* Starts a transaction that can later be committed or rolled back.
|
||||
*/
|
||||
public function startTransaction(): void;
|
||||
|
||||
/*
|
||||
* Quits a current transaction without saving.
|
||||
*/
|
||||
public function rollback(): void;
|
||||
|
||||
/*
|
||||
* Saves and exits a current transaction.
|
||||
*/
|
||||
public function commit(): void;
|
||||
|
||||
/**
|
||||
* Returns the primary key from the last inserted row.
|
||||
*/
|
||||
public function GetLastInsertedId(): int;
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class MimeType
|
||||
{
|
||||
public const PLAINTEXT = 'text/plain';
|
||||
public const JSON = 'application/json';
|
||||
public const SVG = 'image/svg+xml';
|
||||
}
|
|
@ -1,286 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class MySqlDatabase implements DatabaseInterface
|
||||
{
|
||||
private const CHARS_ALLOWED_IN_TABLE_NAMES = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_-';
|
||||
|
||||
private ?PDO $connection;
|
||||
private ?PDOStatement $cursor;
|
||||
private bool $isTransactionOpen = false;
|
||||
|
||||
public function __construct(
|
||||
string $hostname = Setting::MYSQL_HOST,
|
||||
string $user = Setting::MYSQL_USER,
|
||||
string $password = Setting::MYSQL_PASSWORD,
|
||||
string $database = Setting::MYSQL_DATABASE
|
||||
) {
|
||||
$this->connection = new PDO("mysql:host=$hostname;dbname=$database", $user, $password);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->connection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function Query(string $query, array $params = []): void
|
||||
{
|
||||
$this->cursor = $this->connection->prepare($query);
|
||||
|
||||
if (!$this->cursor) {
|
||||
throw new Exception('Initialization of database cursor failed');
|
||||
}
|
||||
|
||||
foreach ($params as $key => $param) {
|
||||
if (is_bool($param)) {
|
||||
$param = (int)$param;
|
||||
}
|
||||
|
||||
$this->cursor->bindValue(':' . $key, $param);
|
||||
}
|
||||
|
||||
if (!$this->cursor->execute()) {
|
||||
throw new Exception($this->cursor->errorInfo()[2]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getResult(): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
while ($fetch = $this->cursor->fetchObject()) {
|
||||
$row = [];
|
||||
|
||||
foreach (get_object_vars($fetch) as $key => $value) {
|
||||
$row[$key] = $value;
|
||||
}
|
||||
|
||||
$result[] = $row;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects data from a table.
|
||||
*/
|
||||
public function Select(
|
||||
string $tableName,
|
||||
array $fields = [],
|
||||
array $conditions = [],
|
||||
int $limit = 0,
|
||||
array $orderBy = [],
|
||||
bool $asc = true,
|
||||
int $offset = 0
|
||||
): array {
|
||||
if (!self::isValidTableName($tableName)) {
|
||||
[];
|
||||
}
|
||||
|
||||
if (count($fields) === 0) {
|
||||
$fieldsExpression = '*';
|
||||
} else {
|
||||
$fieldsExpression = implode(',', $fields);
|
||||
}
|
||||
|
||||
$conditionsExpression = '';
|
||||
$conditionPairs = [];
|
||||
|
||||
foreach ($conditions as $field => $value) {
|
||||
$conditionPairs[] = sprintf('%s = :%s ', $field, $field);
|
||||
}
|
||||
|
||||
if (count($conditions) > 0) {
|
||||
$conditionsExpression = 'WHERE ';
|
||||
$conditionsExpression .= implode(' AND ', $conditionPairs);
|
||||
}
|
||||
|
||||
$orderStatement = '';
|
||||
|
||||
if (count($orderBy) > 0) {
|
||||
$orderStatement = 'ORDER BY ' . implode(',', $orderBy);
|
||||
|
||||
if (!$asc) {
|
||||
$orderStatement .= ' DESC';
|
||||
}
|
||||
}
|
||||
|
||||
$limitStatement = '';
|
||||
|
||||
if ($limit > 0) {
|
||||
$limitStatement = 'LIMIT ' . $limit;
|
||||
}
|
||||
|
||||
$offsetStatement = '';
|
||||
|
||||
if ($offset > 0) {
|
||||
$offsetStatement = 'OFFSET ' . $offset;
|
||||
}
|
||||
|
||||
$query = sprintf(
|
||||
'SELECT %s FROM %s %s %s %s %s',
|
||||
$fieldsExpression,
|
||||
$tableName,
|
||||
$conditionsExpression,
|
||||
$orderStatement,
|
||||
$limitStatement,
|
||||
$offsetStatement
|
||||
);
|
||||
|
||||
try {
|
||||
$this->Query($query, $conditions);
|
||||
} catch (Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes rows from a table.
|
||||
*/
|
||||
public function Delete(string $table, array $conditions): void
|
||||
{
|
||||
if (count($conditions) === 0) {
|
||||
$conditionsStatement = '1';
|
||||
} else {
|
||||
$conditionPairs = [];
|
||||
|
||||
foreach ($conditions as $field => $value) {
|
||||
$conditionPairs[] = sprintf('%s=:Condition%s', $field, $field);
|
||||
$conditions['Condition' . $field] = $value;
|
||||
unset($conditions[$field]);
|
||||
}
|
||||
|
||||
$conditionsStatement = implode(' AND ', $conditionPairs);
|
||||
}
|
||||
|
||||
$query = sprintf('DELETE FROM %s WHERE %s', $table, $conditionsStatement);
|
||||
|
||||
$this->Query($query, $conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function Insert(string $table, array $fields): ?int
|
||||
{
|
||||
if (count($fields) === 0) {
|
||||
throw new Exception('Row to insert is empty!');
|
||||
}
|
||||
|
||||
$fieldNames = implode(',', array_keys($fields));
|
||||
$fieldPlaceholder = [];
|
||||
|
||||
foreach ($fields as $name => $value) {
|
||||
$fieldPlaceholder[] = ':' . $name;
|
||||
}
|
||||
|
||||
$query = sprintf(
|
||||
'INSERT INTO %s (%s) VALUES (%s)', $table, $fieldNames, implode(',', $fieldPlaceholder)
|
||||
);
|
||||
|
||||
$this->Query($query, $fields);
|
||||
|
||||
$lastInsertedId = $this->GetLastInsertedId();
|
||||
|
||||
if ((int)$lastInsertedId === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $lastInsertedId;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function Update(string $table, array $fields, array $conditions): void
|
||||
{
|
||||
$conditionPairs = [];
|
||||
|
||||
foreach ($conditions as $field => $value) {
|
||||
$conditionPairs[] = sprintf('%s=:Condition%s', $field, $field);
|
||||
$conditions['Condition' . $field] = $value;
|
||||
unset($conditions[$field]);
|
||||
}
|
||||
|
||||
$conditionsStatement = implode(' AND ', $conditionPairs);
|
||||
|
||||
$fieldPairs = [];
|
||||
|
||||
foreach ($fields as $field => $value) {
|
||||
$fieldPairs[] = sprintf('%s=:%s', $field, $field);
|
||||
}
|
||||
|
||||
$fieldsStatement = implode(',', $fieldPairs);
|
||||
|
||||
$query = sprintf('UPDATE %s SET %s WHERE %s', $table, $fieldsStatement, $conditionsStatement);
|
||||
|
||||
|
||||
$this->Query($query, array_merge($fields, $conditions));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function Count(string $table, array $conditions = []): int
|
||||
{
|
||||
$result = $this->Select($table, ['count(*)'], $conditions);
|
||||
|
||||
return (int)$result[0]['count(*)'];
|
||||
}
|
||||
|
||||
public function hasTransaction(): bool
|
||||
{
|
||||
return $this->isTransactionOpen;
|
||||
}
|
||||
|
||||
public function startTransaction(): void
|
||||
{
|
||||
$this->connection->beginTransaction();
|
||||
$this->isTransactionOpen = true;
|
||||
}
|
||||
|
||||
public function rollback(): void
|
||||
{
|
||||
$this->connection->rollBack();
|
||||
$this->isTransactionOpen = false;
|
||||
}
|
||||
|
||||
public function commit(): void
|
||||
{
|
||||
$this->connection->commit();
|
||||
$this->isTransactionOpen = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function GetLastInsertedId(): int
|
||||
{
|
||||
$this->Query('SELECT LAST_INSERT_ID() as ID');
|
||||
|
||||
return (int)$this->getResult()[0]['ID'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a check if the given table name contains forbidden chars.
|
||||
*/
|
||||
private static function isValidTableName(string $tableName): bool
|
||||
{
|
||||
foreach (str_split($tableName) as $char) {
|
||||
if (substr_count(self::CHARS_ALLOWED_IN_TABLE_NAMES, $char) === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
class MySqlTable extends Table
|
||||
{
|
||||
public function __construct(string $tableName, $id = null, DatabaseInterface &$database = null)
|
||||
{
|
||||
self::EnsureConnection($database);
|
||||
|
||||
parent::__construct($tableName, $id, $database);
|
||||
}
|
||||
|
||||
public static function EnsureConnection(?DatabaseInterface & $database): void
|
||||
{
|
||||
if (!($database instanceof MySqlDatabase)) {
|
||||
$database = new MySqlDatabase();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class Password
|
||||
{
|
||||
public static function IsValid(string $password, string $hash): bool
|
||||
{
|
||||
return password_verify($password, $hash);
|
||||
}
|
||||
|
||||
public static function GetHash(string $password): string
|
||||
{
|
||||
return password_hash($password, PASSWORD_BCRYPT);
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class QrCode
|
||||
{
|
||||
private int $fingerprintId;
|
||||
private string $fingerprint;
|
||||
private string $temporaryFilename;
|
||||
|
||||
public function __construct(int $fingerprintId, string $fingerprint)
|
||||
{
|
||||
$this->fingerprintId = $fingerprintId;
|
||||
$this->fingerprint = $fingerprint;
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
if (!is_file($this->temporaryFilename)) {
|
||||
throw new QrCodeException(
|
||||
sprintf('Temporary QR file %s couldn\'t be found!', $this->temporaryFilename)
|
||||
);
|
||||
}
|
||||
|
||||
$returnCode = 0;
|
||||
|
||||
$path = substr(Setting::PATH_QR_CODES, -1) === '/'
|
||||
? Setting::PATH_QR_CODES
|
||||
: Setting::PATH_QR_CODES . '/';
|
||||
|
||||
$filename = $path . $this->fingerprintId . '.svg';
|
||||
|
||||
passthru(
|
||||
sprintf('mv %s %s', $this->temporaryFilename, $filename),
|
||||
$returnCode
|
||||
);
|
||||
|
||||
if ($returnCode !== 0 || !is_file($filename)) {
|
||||
throw new QrCodeException(
|
||||
sprintf('QR code for fingerprint %d couldn\'t be created!', $this->fingerprintId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function generate(): bool
|
||||
{
|
||||
$returnCode = 0;
|
||||
|
||||
$path = substr(Setting::PATH_TMP, -1) === '/' ? Setting::PATH_TMP : Setting::PATH_TMP . '/';
|
||||
|
||||
$this->temporaryFilename = $path . $this->generateTemporaryFilename() . '.svg';
|
||||
|
||||
passthru(
|
||||
sprintf('qrencode -o %s -t SVG "%s"', $this->temporaryFilename, $this->fingerprint),
|
||||
$returnCode
|
||||
);
|
||||
|
||||
return !(bool)$returnCode;
|
||||
}
|
||||
|
||||
private function generateTemporaryFilename(): string
|
||||
{
|
||||
$hash = hash('md5', (new DateTime())->format('U') . $this->fingerprint);
|
||||
|
||||
return sprintf('%s.svg', $hash);
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class Router
|
||||
{
|
||||
private string $route;
|
||||
private string $method;
|
||||
private ?string $requestBody = null;
|
||||
private ?string $contentType = null;
|
||||
|
||||
public function __construct(string $route, string $method)
|
||||
{
|
||||
$this->route = $route;
|
||||
$this->method = $method;
|
||||
}
|
||||
|
||||
public function setRequestBody(string $contentType, string $content): void
|
||||
{
|
||||
$this->contentType = $contentType;
|
||||
$this->requestBody = $content;
|
||||
}
|
||||
|
||||
public function route(): void
|
||||
{
|
||||
foreach (ROUTES[$this->method] as $route => $params) {
|
||||
preg_match_all($this->createRegex($route, $params['params']), $this->route, $matches);
|
||||
|
||||
if (count($matches[0]) > 0) {
|
||||
$class = new ReflectionClass($params['controller']);
|
||||
|
||||
$controller = $class->newInstance($matches[0][0]);
|
||||
|
||||
if ($this->requestBody !== null && $this->contentType !== null) {
|
||||
$controller->setRequestBody($this->contentType, $this->requestBody);
|
||||
}
|
||||
|
||||
$controller->handle();
|
||||
$controller->getResponse()->respond();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createRegex(string $route, array $params): string
|
||||
{
|
||||
foreach ($params as $param => $values) {
|
||||
switch ($values['type']) {
|
||||
case 'int':
|
||||
$route = str_replace('{' . $param . '}', '[0-9]+', $route);
|
||||
}
|
||||
}
|
||||
|
||||
return '/' . str_replace('/', '\\/', $route) . '/';
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class ServerStatus
|
||||
{
|
||||
public const OK = 200;
|
||||
public const FORBIDDEN = 403;
|
||||
public const UNAUTHORIZED = 401;
|
||||
public const BAD_REQUEST = 400;
|
||||
public const NOT_FOUND = 404;
|
||||
public const INTERNAL_ERROR = 500;
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class Session
|
||||
{
|
||||
public const TYPE_INT = 1;
|
||||
public const TYPE_STRING = 2;
|
||||
public const TYPE_BOOL = 3;
|
||||
|
||||
private const IS_LOGGED_IN = 'is_logged_in';
|
||||
private const USER_ID = 'account_id';
|
||||
private const USERNAME = 'username';
|
||||
private const PERMISSION = 'permission';
|
||||
private const EMAIL = 'email';
|
||||
private const JABBER_ADDRESS = 'jabber';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@session_start();
|
||||
|
||||
if (!$this->HasValue(self::IS_LOGGED_IN)) {
|
||||
$this->SetBool(self::IS_LOGGED_IN, false);
|
||||
}
|
||||
}
|
||||
|
||||
public function Destroy(): void
|
||||
{
|
||||
session_unset();
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
public function Login(string $usernameOrEmail, string $password): bool
|
||||
{
|
||||
try {
|
||||
$user = User::getFromUsername($usernameOrEmail);
|
||||
} catch (Throwable $e) {
|
||||
$user = User::getFromEmail($usernameOrEmail);
|
||||
}
|
||||
|
||||
if ($user === null || !Password::IsValid($password, $user->getPassword())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->SetBool(self::IS_LOGGED_IN, true);
|
||||
$this->SetInt(self::USER_ID, $user->getPrimaryKey());
|
||||
$this->SetString(self::USERNAME, $user->getUsername());
|
||||
$this->SetString(self::EMAIL, $user->getEmail());
|
||||
$this->SetString(self::JABBER_ADDRESS, $user->getJabberAddress());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function HasValue(string $key): bool
|
||||
{
|
||||
return self::HasSession() && isset($_SESSION[$key]);
|
||||
}
|
||||
|
||||
public function SetBool(string $key, bool $value): void
|
||||
{
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
|
||||
public function SetString(string $key, string $value): void
|
||||
{
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
|
||||
public function SetInt(string $key, int $value): void
|
||||
{
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
|
||||
public function IsLoggedIn(): bool
|
||||
{
|
||||
return self::HasSession() && $this->GetBool(self::IS_LOGGED_IN);
|
||||
}
|
||||
|
||||
public function GetInt(string $key): ?int
|
||||
{
|
||||
return $this->HasValue($key) ? (int)$_SESSION[$key] : null;
|
||||
}
|
||||
|
||||
public function GetString(string $key): ?string
|
||||
{
|
||||
return $this->HasValue($key) ? (string)$_SESSION[$key] : null;
|
||||
}
|
||||
|
||||
public function GetBool(string $key): ?bool
|
||||
{
|
||||
return $this->HasValue($key) ? (bool)$_SESSION[$key] : null;
|
||||
}
|
||||
|
||||
public function GetAccountId(): ?int
|
||||
{
|
||||
return $this->GetInt(self::USER_ID);
|
||||
}
|
||||
|
||||
public function GetPermission(): ?int
|
||||
{
|
||||
return $this->GetInt(self::PERMISSION);
|
||||
}
|
||||
|
||||
public static function HasSession(): bool
|
||||
{
|
||||
return isset($_SESSION);
|
||||
}
|
||||
}
|
|
@ -1,284 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Basic object to load a mysql table inside an object.
|
||||
*/
|
||||
abstract class Table
|
||||
{
|
||||
public const TYPE_STRING = 1;
|
||||
public const TYPE_INTEGER = 2;
|
||||
public const TYPE_FLOAT = 3;
|
||||
public const TYPE_DATETIME = 4;
|
||||
public const TYPE_BOOL = 5;
|
||||
|
||||
protected const VALUE = 'value';
|
||||
protected const TYPE = 'type';
|
||||
|
||||
protected ?DatabaseInterface $database;
|
||||
protected string $tableName;
|
||||
protected array $fields;
|
||||
protected string $primaryKey;
|
||||
protected bool $isPrimKeyManual = false;
|
||||
|
||||
public function __construct(string $tableName, $id, ?DatabaseInterface & $database)
|
||||
{
|
||||
$this->tableName = $tableName;
|
||||
$this->fields = [];
|
||||
|
||||
$this->database = $database;
|
||||
|
||||
$this->database->Query(sprintf('DESCRIBE %s', $tableName));
|
||||
|
||||
$result = $this->database->getResult();
|
||||
|
||||
foreach ($result as $field) {
|
||||
$sqlType = substr_count(
|
||||
$field['Type'], '(') === 0 ? $field['Type'] : strstr($field['Type'],
|
||||
'(',
|
||||
true
|
||||
);
|
||||
|
||||
switch ($sqlType) {
|
||||
case 'varchar':
|
||||
case 'char':
|
||||
case 'text':
|
||||
case 'longtext':
|
||||
case 'mediumtext':
|
||||
case 'tinytext':
|
||||
$type = self::TYPE_STRING;
|
||||
break;
|
||||
case 'int':
|
||||
case 'smallint':
|
||||
case 'mediumint':
|
||||
case 'bigint':
|
||||
$type = self::TYPE_INTEGER;
|
||||
break;
|
||||
case 'float':
|
||||
case 'decimal':
|
||||
case 'double':
|
||||
case 'real':
|
||||
$type = self::TYPE_FLOAT;
|
||||
break;
|
||||
case 'datetime':
|
||||
case 'date':
|
||||
$type = self::TYPE_DATETIME;
|
||||
break;
|
||||
case 'tinyint':
|
||||
$type = self::TYPE_BOOL;
|
||||
break;
|
||||
default:
|
||||
throw new Exception(sprintf('Type %s of field %s couldn\'t be handled', $sqlType, $field['Field']));
|
||||
}
|
||||
|
||||
$this->addField($field['Field'], $type);
|
||||
|
||||
if ($field['Key'] === 'PRI') {
|
||||
$this->primaryKey = $field['Field'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->isPrimKeyManual && $id !== null) {
|
||||
$this->loadById($id);
|
||||
}
|
||||
}
|
||||
|
||||
public function getPrimaryKey()
|
||||
{
|
||||
if ($this->primaryKey === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getField($this->primaryKey);
|
||||
}
|
||||
|
||||
protected function addField(string $name, int $type): void
|
||||
{
|
||||
if (!self::IsValidType($type)) {
|
||||
throw new Exception(
|
||||
sprintf('Field %s has invalid type of %s!', $name, $type)
|
||||
);
|
||||
}
|
||||
|
||||
$this->fields[$name] = [self::VALUE => null, self::TYPE => $type];
|
||||
}
|
||||
|
||||
protected function loadById($id): void
|
||||
{
|
||||
try {
|
||||
$this->database->Query(
|
||||
sprintf('SELECT * FROM %s WHERE %s = :id', $this->tableName, $this->primaryKey),
|
||||
['id' => $id]
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
$result = $this->database->getResult();
|
||||
|
||||
if (count($result) === 0) {
|
||||
throw new Exception('No table entry with id ' . $id . ' found!');
|
||||
}
|
||||
|
||||
foreach ($result[0] as $field => $value) {
|
||||
$this->setField($field, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function Flush(): void
|
||||
{
|
||||
$this->database->Delete($this->tableName, []);
|
||||
}
|
||||
|
||||
public function Delete(): void
|
||||
{
|
||||
try {
|
||||
$this->database->Delete($this->tableName, [$this->primaryKey => $this->getPrimaryKey()]);
|
||||
} catch (Throwable $e) {
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
foreach ($this->GetAllFieldNames() as $field) {
|
||||
$this->fields[$field][self::VALUE] = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getField(string $name)
|
||||
{
|
||||
if (!array_key_exists($name, $this->fields)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->fields[$name][self::VALUE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value for the given field inside the database.
|
||||
*/
|
||||
protected function setField(string $name, $value): void
|
||||
{
|
||||
if (!$this->HasField($name)) {
|
||||
throw new Exception(sprintf('Field %s doesn\'t exist!', $name));
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
$this->fields[$name][self::VALUE] = null;
|
||||
return;
|
||||
}
|
||||
|
||||
switch ($this->fields[$name][self::TYPE]) {
|
||||
case self::TYPE_STRING:
|
||||
$this->fields[$name][self::VALUE] = (string)$value;
|
||||
return;
|
||||
case self::TYPE_INTEGER:
|
||||
$this->fields[$name][self::VALUE] = (int)$value;
|
||||
return;
|
||||
case self::TYPE_FLOAT:
|
||||
$this->fields[$name][self::VALUE] = (float)$value;
|
||||
return;
|
||||
case self::TYPE_DATETIME:
|
||||
try {
|
||||
$this->fields[$name][self::VALUE] = new DateTime((string)$value);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception();
|
||||
}
|
||||
return;
|
||||
case self::TYPE_BOOL:
|
||||
$this->fields[$name][self::VALUE] = (bool)$value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the table has the given column.
|
||||
*/
|
||||
public function HasField(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, $this->fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the whole object into the database.
|
||||
*/
|
||||
public function Save(): void
|
||||
{
|
||||
$fields = [];
|
||||
|
||||
foreach ($this->GetAllFieldNames() as $fieldName) {
|
||||
$field = $this->getField($fieldName);
|
||||
|
||||
if ($field instanceof DateTime) {
|
||||
$fields[$fieldName] = $field->format('Y-m-d H:i:s');
|
||||
} else if (is_bool($field)) {
|
||||
$fields[$fieldName] = (int)$field;
|
||||
} else {
|
||||
$fields[$fieldName] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isPrimKeyManual) {
|
||||
$this->saveWithManualId($fields);
|
||||
} else {
|
||||
$this->saveWithPrimaryKey($fields);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function GetAllFieldNames(): array
|
||||
{
|
||||
$fieldNames = [];
|
||||
|
||||
foreach ($this->fields as $name => $field) {
|
||||
$fieldNames[] = $name;
|
||||
}
|
||||
|
||||
return $fieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the index is a valid data type.
|
||||
*/
|
||||
public static function IsValidType(int $type): bool
|
||||
{
|
||||
$validTypes = [
|
||||
self::TYPE_STRING,
|
||||
self::TYPE_INTEGER,
|
||||
self::TYPE_FLOAT,
|
||||
self::TYPE_DATETIME,
|
||||
self::TYPE_BOOL,
|
||||
];
|
||||
|
||||
return in_array($type, $validTypes);
|
||||
}
|
||||
|
||||
protected function saveWithManualId(array $fields): void
|
||||
{
|
||||
if ($this->getField($this->primaryKey) === null) {
|
||||
throw new Exception('Manual primary key must not be null!');
|
||||
}
|
||||
|
||||
$hasKey = (bool)$this->database->Count(
|
||||
$this->tableName,
|
||||
[$this->primaryKey => $this->getField($this->primaryKey)]
|
||||
);
|
||||
|
||||
if ($hasKey) {
|
||||
$this->database->Update(
|
||||
$this->tableName, $fields, [$this->primaryKey => $this->getField($this->primaryKey)]
|
||||
);
|
||||
} else {
|
||||
$this->database->Insert($this->tableName, $fields);
|
||||
}
|
||||
}
|
||||
|
||||
protected function saveWithPrimaryKey(array $fields): void
|
||||
{
|
||||
if ($this->getField($this->primaryKey) !== null) {
|
||||
$this->database->Update(
|
||||
$this->tableName, $fields, [$this->primaryKey => $this->getField($this->primaryKey)]
|
||||
);
|
||||
} else {
|
||||
$this->setField($this->primaryKey, $this->database->Insert($this->tableName, $fields));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class Fingerprint extends MySqlTable implements JsonSerializable
|
||||
{
|
||||
public const FIELD_ID = 'FingerprintId';
|
||||
public const FIELD_FINGERPRINT = 'Fingerprint';
|
||||
public const FIELD_USER = 'UserId';
|
||||
|
||||
public function __construct($id = null, DatabaseInterface &$database = null)
|
||||
{
|
||||
parent::__construct(self::class, $id, $database);
|
||||
}
|
||||
|
||||
public function getFingerprintId(): ?int
|
||||
{
|
||||
if ($this->getPrimaryKey() === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int)$this->getPrimaryKey();
|
||||
}
|
||||
|
||||
public function getFingerprint(): string
|
||||
{
|
||||
return $this->getField(self::FIELD_FINGERPRINT);
|
||||
}
|
||||
|
||||
public function getUserId(): int
|
||||
{
|
||||
return $this->getField(self::FIELD_USER);
|
||||
}
|
||||
|
||||
public function setFingerprint(string $fingerprint): void
|
||||
{
|
||||
$this->setField(self::FIELD_FINGERPRINT, $fingerprint);
|
||||
}
|
||||
|
||||
public function setUserId(int $userId): void
|
||||
{
|
||||
$this->setField(self::FIELD_USER, $userId);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'fingerprintId' => $this->getFingerprintId(),
|
||||
'fingerprint' => $this->getFingerprint(),
|
||||
'userId' => $this->getUserId()
|
||||
];
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class Sharing extends MySqlTable
|
||||
{
|
||||
public const FIELD_USER = 'User';
|
||||
public const FIELD_USER_SHARED = 'UserShared';
|
||||
|
||||
public function __construct($id = null, DatabaseInterface &$database = null)
|
||||
{
|
||||
parent::__construct(self::class, $id, $database);
|
||||
}
|
||||
|
||||
public function getSharingId(): ?int
|
||||
{
|
||||
if ($this->getPrimaryKey() === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int)$this->getPrimaryKey();
|
||||
}
|
||||
|
||||
public function getUserId(): int
|
||||
{
|
||||
return $this->getField(self::FIELD_USER);
|
||||
}
|
||||
|
||||
public function getUserShared(): int
|
||||
{
|
||||
return $this->getField(self::FIELD_USER_SHARED);
|
||||
}
|
||||
|
||||
public function setUserId(int $userId): void
|
||||
{
|
||||
$this->setField(self::FIELD_USER, $userId);
|
||||
}
|
||||
|
||||
public function setUserShared(int $userShared): void
|
||||
{
|
||||
$this->setField(self::FIELD_USER_SHARED, $userShared);
|
||||
}
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class User extends MySqlTable implements JsonSerializable
|
||||
{
|
||||
public const FIELD_ID = 'UserId';
|
||||
public const FIELD_USERNAME = 'Username';
|
||||
public const FIELD_PASSWORD = 'Password';
|
||||
public const FIELD_EMAIL = 'Email';
|
||||
public const FIELD_JABBER_ADDRESS = 'JabberAddress';
|
||||
|
||||
public function __construct($id = null, DatabaseInterface &$database = null)
|
||||
{
|
||||
parent::__construct(self::class, $id, $database);
|
||||
}
|
||||
|
||||
public function getUserId(): ?int
|
||||
{
|
||||
if ($this->getPrimaryKey() === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int)$this->getPrimaryKey();
|
||||
}
|
||||
|
||||
public function getUsername(): string
|
||||
{
|
||||
return $this->getField(self::FIELD_USERNAME);
|
||||
}
|
||||
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->getField(self::FIELD_PASSWORD);
|
||||
}
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->getField(self::FIELD_EMAIL);
|
||||
}
|
||||
|
||||
public function getJabberAddress(): string
|
||||
{
|
||||
return $this->getField(self::FIELD_JABBER_ADDRESS);
|
||||
}
|
||||
|
||||
public function setUsername(string $username): void
|
||||
{
|
||||
$this->setField(self::FIELD_USERNAME, $username);
|
||||
}
|
||||
|
||||
public function setPassword(string $password): void
|
||||
{
|
||||
$this->setField(self::FIELD_PASSWORD, $password);
|
||||
}
|
||||
|
||||
public function setEmail(string $email): void
|
||||
{
|
||||
$this->setField(self::FIELD_EMAIL, $email);
|
||||
}
|
||||
|
||||
public function setJabberAddress(string $jabberAddress): void
|
||||
{
|
||||
$this->setField(self::FIELD_JABBER_ADDRESS, $jabberAddress);
|
||||
}
|
||||
|
||||
public static function getFromUsername(string $username, DatabaseInterface &$database = null): self
|
||||
{
|
||||
$databaseGiven = true;
|
||||
|
||||
if ($database === null) {
|
||||
$database = new MySqlDatabase();
|
||||
$databaseGiven = false;
|
||||
}
|
||||
|
||||
if ($database->Count(self::class) === 0) {
|
||||
throw new UserException(sprintf('No user with name %s found!', $username));
|
||||
}
|
||||
|
||||
$id = $database->Select(self::class, [self::FIELD_ID], [self::FIELD_USERNAME => $username]);
|
||||
|
||||
$user = $databaseGiven ? new User((int)$id, $database) : new User((int)$id);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public static function getFromEmail(string $email, DatabaseInterface &$database = null): self
|
||||
{
|
||||
$databaseGiven = true;
|
||||
|
||||
if ($database === null) {
|
||||
$database = new MySqlDatabase();
|
||||
$databaseGiven = false;
|
||||
}
|
||||
|
||||
if ($database->Count(self::class) === 0) {
|
||||
throw new UserException(sprintf('No user with email %s found!', $email));
|
||||
}
|
||||
|
||||
$id = $database->Select(self::class, [self::FIELD_ID], [self::FIELD_EMAIL => $email])[0][self::FIELD_ID];
|
||||
|
||||
$user = $databaseGiven ? new User((int)$id, $database) : new User((int)$id);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function getFingerprintIds(): array
|
||||
{
|
||||
$result = $this->database->Select(
|
||||
Fingerprint::class,
|
||||
[Fingerprint::FIELD_ID],
|
||||
[Fingerprint::FIELD_USER => $this->getUserId()]
|
||||
);
|
||||
|
||||
$ids = [];
|
||||
|
||||
foreach ($result as $record) {
|
||||
$ids[] = (int)$record[Fingerprint::FIELD_ID];
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return [
|
||||
'userId' => $this->getUserId(),
|
||||
'username' => $this->getUsername(),
|
||||
'jabberAddress' => $this->getJabberAddress(),
|
||||
'fingerprintIds' => $this->getFingerprintIds()
|
||||
];
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class DatabaseException extends Exception
|
||||
{
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
<?php
|
||||
|
||||
require 'data/classes/core/Autoloader.php';
|
||||
|
||||
Autoloader::BuildCache();
|
Loading…
Reference in New Issue