Merge branch 'v1.8'

This commit is contained in:
maxpozdeev 2023-08-06 19:12:45 +03:00
commit 4b4e47df6a
39 changed files with 1189 additions and 203 deletions

View file

@ -3,7 +3,8 @@
"vendor-dir": "src/includes/vendor"
},
"require": {
"erusev/parsedown": "^1.7"
"erusev/parsedown": "^1.7",
"symfony/polyfill-intl-normalizer": "^1.27"
},
"require-dev": {
"league/commonmark": "^2.3"

180
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "18661f8c2155d955e7962271812bbed0",
"content-hash": "cb6818ea27ab475ab65854fa66fd68bd",
"packages": [
{
"name": "erusev/parsedown",
@ -55,21 +55,105 @@
"source": "https://github.com/erusev/parsedown/tree/1.7.x"
},
"time": "2019-12-30T22:54:17+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6",
"reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2022-11-03T14:55:06+00:00"
}
],
"packages-dev": [
{
"name": "dflydev/dot-access-data",
"version": "v3.0.1",
"version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/dflydev/dflydev-dot-access-data.git",
"reference": "0992cc19268b259a39e86f296da5f0677841f42c"
"reference": "f41715465d65213d644d3141a6a93081be5d3549"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c",
"reference": "0992cc19268b259a39e86f296da5f0677841f42c",
"url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549",
"reference": "f41715465d65213d644d3141a6a93081be5d3549",
"shasum": ""
},
"require": {
@ -80,7 +164,7 @@
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
"scrutinizer/ocular": "1.6.0",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "^3.14"
"vimeo/psalm": "^4.0.0"
},
"type": "library",
"extra": {
@ -129,22 +213,22 @@
],
"support": {
"issues": "https://github.com/dflydev/dflydev-dot-access-data/issues",
"source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1"
"source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2"
},
"time": "2021-08-13T13:06:58+00:00"
"time": "2022-10-27T11:44:00+00:00"
},
{
"name": "league/commonmark",
"version": "2.3.4",
"version": "2.3.7",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "155ec1c95626b16fda0889cf15904d24890a60d5"
"reference": "a36bd2be4f5387c0f3a8792a0d76b7d68865abbf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/155ec1c95626b16fda0889cf15904d24890a60d5",
"reference": "155ec1c95626b16fda0889cf15904d24890a60d5",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/a36bd2be4f5387c0f3a8792a0d76b7d68865abbf",
"reference": "a36bd2be4f5387c0f3a8792a0d76b7d68865abbf",
"shasum": ""
},
"require": {
@ -164,15 +248,15 @@
"erusev/parsedown": "^1.0",
"ext-json": "*",
"github/gfm": "0.29.0",
"michelf/php-markdown": "^1.4",
"michelf/php-markdown": "^1.4 || ^2.0",
"nyholm/psr7": "^1.5",
"phpstan/phpstan": "^0.12.88 || ^1.0.0",
"phpunit/phpunit": "^9.5.5",
"phpstan/phpstan": "^1.8.2",
"phpunit/phpunit": "^9.5.21",
"scrutinizer/ocular": "^1.8.1",
"symfony/finder": "^5.3",
"symfony/finder": "^5.3 | ^6.0",
"symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0",
"unleashedtech/php-coding-standard": "^3.1",
"vimeo/psalm": "^4.7.3"
"unleashedtech/php-coding-standard": "^3.1.1",
"vimeo/psalm": "^4.24.0"
},
"suggest": {
"symfony/yaml": "v2.3+ required if using the Front Matter extension"
@ -237,7 +321,7 @@
"type": "tidelift"
}
],
"time": "2022-07-17T16:25:47+00:00"
"time": "2022-11-03T17:29:46+00:00"
},
{
"name": "league/config",
@ -323,25 +407,25 @@
},
{
"name": "nette/schema",
"version": "v1.2.2",
"version": "v1.2.3",
"source": {
"type": "git",
"url": "https://github.com/nette/schema.git",
"reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df"
"reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df",
"reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df",
"url": "https://api.github.com/repos/nette/schema/zipball/abbdbb70e0245d5f3bf77874cea1dfb0c930d06f",
"reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f",
"shasum": ""
},
"require": {
"nette/utils": "^2.5.7 || ^3.1.5 || ^4.0",
"php": ">=7.1 <8.2"
"php": ">=7.1 <8.3"
},
"require-dev": {
"nette/tester": "^2.3 || ^2.4",
"phpstan/phpstan-nette": "^0.12",
"phpstan/phpstan-nette": "^1.0",
"tracy/tracy": "^2.7"
},
"type": "library",
@ -379,26 +463,26 @@
],
"support": {
"issues": "https://github.com/nette/schema/issues",
"source": "https://github.com/nette/schema/tree/v1.2.2"
"source": "https://github.com/nette/schema/tree/v1.2.3"
},
"time": "2021-10-15T11:40:02+00:00"
"time": "2022-10-13T01:24:26+00:00"
},
{
"name": "nette/utils",
"version": "v3.2.7",
"version": "v3.2.8",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99"
"reference": "02a54c4c872b99e4ec05c4aec54b5a06eb0f6368"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/0af4e3de4df9f1543534beab255ccf459e7a2c99",
"reference": "0af4e3de4df9f1543534beab255ccf459e7a2c99",
"url": "https://api.github.com/repos/nette/utils/zipball/02a54c4c872b99e4ec05c4aec54b5a06eb0f6368",
"reference": "02a54c4c872b99e4ec05c4aec54b5a06eb0f6368",
"shasum": ""
},
"require": {
"php": ">=7.2 <8.2"
"php": ">=7.2 <8.3"
},
"conflict": {
"nette/di": "<3.0.6"
@ -464,9 +548,9 @@
],
"support": {
"issues": "https://github.com/nette/utils/issues",
"source": "https://github.com/nette/utils/tree/v3.2.7"
"source": "https://github.com/nette/utils/tree/v3.2.8"
},
"time": "2022-01-24T11:29:14+00:00"
"time": "2022-09-12T23:36:20+00:00"
},
{
"name": "psr/event-dispatcher",
@ -520,16 +604,16 @@
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.1.1",
"version": "v3.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918"
"reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
"reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3",
"reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3",
"shasum": ""
},
"require": {
@ -538,7 +622,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.1-dev"
"dev-main": "3.3-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -567,7 +651,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0"
},
"funding": [
{
@ -583,20 +667,20 @@
"type": "tidelift"
}
],
"time": "2022-02-25T11:15:52+00:00"
"time": "2022-11-25T10:21:52+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.26.0",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
"reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
"shasum": ""
},
"require": {
@ -605,7 +689,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.26-dev"
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
@ -650,7 +734,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
"source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0"
},
"funding": [
{
@ -666,7 +750,7 @@
"type": "tidelift"
}
],
"time": "2022-05-10T07:21:04+00:00"
"time": "2022-11-03T14:55:06+00:00"
}
],
"aliases": [],

View file

@ -0,0 +1,23 @@
FROM alpine:latest
RUN set -x \
&& apk add --no-cache ca-certificates curl \
&& curl -o /etc/apk/keys/angie-signing.rsa https://angie.software/keys/angie-signing.rsa \
&& echo "https://download.angie.software/angie/alpine/v$(egrep -o '[0-9]+\.[0-9]+' /etc/alpine-release)/main" >> /etc/apk/repositories \
&& apk add --no-cache angie \
&& rm /etc/apk/keys/angie-signing.rsa \
&& ln -sf /dev/stdout /var/log/angie/access.log \
&& ln -sf /dev/stderr /var/log/angie/error.log
EXPOSE 80
CMD ["angie", "-g", "daemon off;"]
RUN mv /etc/angie/http.d/default.conf /etc/angie/http.d/default.conf.bak \
&& mkdir /var/log/nginx \
&& ln -sf /var/log/angie/access.log /var/log/nginx/access.log
# config from nginx
COPY default.conf /etc/angie/http.d/default.conf
VOLUME /var/www/html

View file

@ -0,0 +1,5 @@
FROM nginx:alpine
COPY default.conf /etc/nginx/conf.d/
VOLUME /var/www/html

View file

@ -3,7 +3,7 @@ log_format mtt '$remote_addr - $remote_user [$time_local] "$request" $status $
access_log off;
upstream fpm {
server php-fpm:9000;
server fpm:9000;
}
server {

View file

@ -0,0 +1 @@
PLATFORM_NAME=mtt-dev-php72-angie-postgres10

View file

@ -0,0 +1,17 @@
FROM php:7.2-fpm-alpine
RUN apk add --update-cache libpq postgresql-dev && \
docker-php-ext-install pdo_mysql pdo_pgsql && \
apk del postgresql-dev && rm -rf /var/cache/apk/* && \
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
mkdir /var/www/phpinfo && \
echo "<?php phpinfo();" > /var/www/phpinfo/phpinfo.php && \
curl -o /usr/local/bin/phpunit -fL "https://phar.phpunit.de/phpunit-8.phar" && \
chmod +x /usr/local/bin/phpunit && \
ln -s html /var/www/src
#COPY php-mtt.ini /usr/local/etc/php/conf.d/
#COPY php-opcache.ini /usr/local/etc/php/conf.d/
COPY php-fpm-www.conf /usr/local/etc/php-fpm.d/www.conf
VOLUME /var/www/html

View file

@ -0,0 +1,58 @@
version: "3.9"
networks:
network:
name: "${PLATFORM_NAME}-network"
services:
web:
build:
context: ../_nginx
dockerfile: Dockerfile-angie-alpine
image: mtt-dev/angie:alpine
container_name: ${PLATFORM_NAME}-web
ports:
- "8080:80"
volumes:
- ../../../src:/var/www/html
depends_on:
- fpm
networks:
- network
fpm:
build:
context: .
dockerfile: Dockerfile-fpm
image: mtt-dev/php:7.2-fpm-alpine
container_name: ${PLATFORM_NAME}-fpm
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=postgres
- MTT_API_USE_PATH_INFO=YES
- MTT_DB_HOST=db
- MTT_DB_NAME=mtt
- MTT_DB_USER=mtt
- MTT_DB_PASSWORD=mtt
- MTT_DB_PREFIX=mtt_
volumes:
- ../../../src:/var/www/html
- ../../../tests:/var/www/tests
- ./php-mtt.ini:/usr/local/etc/php/conf.d/php-mtt.ini
- ./php-opcache.ini:/usr/local/etc/php/conf.d/php-opcache.ini
networks:
- network
db:
# do not use alpine image due to missing locales
image: postgres:10.0
container_name: ${PLATFORM_NAME}-db
userns_mode: keep-id
environment:
POSTGRES_PASSWORD: mtt
POSTGRES_USER: mtt
POSTGRES_DB: mtt
volumes:
- ../_postgres10/db_data:/var/lib/postgresql/data
networks:
- network

View file

@ -0,0 +1,12 @@
[www]
user = www-data
group = www-data
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 500
access.format = "%R - %u [%t] \"%m %r\" %s %f"

View file

@ -0,0 +1 @@
PLATFORM_NAME=mtt-dev-php72-apache-postgres10

View file

@ -0,0 +1,45 @@
version: "3.9"
networks:
network:
name: "${PLATFORM_NAME}-network"
services:
web:
build:
context: ../mtt-php72-apache/
dockerfile: Dockerfile-web
image: mtt-dev/php:7.2-apache
container_name: ${PLATFORM_NAME}-web
ports:
- "8080:80"
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=postgres
- MTT_DB_HOST=db
- MTT_DB_NAME=mtt
- MTT_DB_USER=mtt
- MTT_DB_PASSWORD=mtt
- MTT_DB_PREFIX=mtt_
volumes:
- ../../../src:/var/www/html
- ../mtt-php72-apache/php-mtt.ini:/usr/local/etc/php/conf.d/php-mtt.ini
- ../mtt-php72-apache/php-opcache.ini:/usr/local/etc/php/conf.d/php-opcache.ini
depends_on:
- db
networks:
- network
db:
# do not use alpine image due to missing locales
image: postgres:10.0
container_name: ${PLATFORM_NAME}-db
userns_mode: keep-id
environment:
POSTGRES_PASSWORD: mtt
POSTGRES_USER: mtt
POSTGRES_DB: mtt
volumes:
- ../_postgres10/db_data:/var/lib/postgresql/data
networks:
- network

View file

@ -1,6 +1,8 @@
FROM php:7.2-apache
FROM php:7.2-apache-buster
RUN docker-php-ext-install mysqli && \
RUN apt-get update && apt-get install -y libpq5 libpq-dev && \
docker-php-ext-install mysqli pdo_pgsql && \
apt-get remove -y libpq-dev && apt-get -y autoremove && \
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
mkdir /var/www/phpinfo && \
echo "<?php\nphpinfo();" > /var/www/phpinfo/phpinfo.php && \
@ -12,5 +14,5 @@ RUN docker-php-ext-install mysqli && \
ln -s html /var/www/src
#COPY php-mtt.ini /usr/local/etc/php/conf.d/
#COPY php-opcache.ini /usr/local/etc/php/conf.d/
COPY php-mtt.ini /usr/local/etc/php/conf.d/
COPY php-opcache.ini /usr/local/etc/php/conf.d/

View file

@ -26,7 +26,6 @@ services:
dockerfile: Dockerfile-fpm
image: mtt-dev/php:7.4-fpm
container_name: ${PLATFORM_NAME}-fpm
hostname: php-fpm
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=sqlite

View file

@ -1 +0,0 @@
PLATFORM_NAME=mtt-dev-php81-nginx

View file

@ -1,11 +0,0 @@
FROM php:8.1-fpm
RUN docker-php-ext-install mysqli && \
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
mkdir /var/www/phpinfo && \
echo "<?php\nphpinfo();" > /var/www/phpinfo/phpinfo.php
#COPY php-mtt.ini /usr/local/etc/php/conf.d/
#COPY php-opcache.ini /usr/local/etc/php/conf.d/
VOLUME /var/www/html

View file

@ -0,0 +1 @@
PLATFORM_NAME=mtt-dev-php82-angie-alpine

View file

@ -0,0 +1,39 @@
version: "3.9"
networks:
network:
name: "${PLATFORM_NAME}-network"
services:
web:
build:
context: ../_nginx
dockerfile: Dockerfile-angie-alpine
image: mtt-dev/angie:alpine
container_name: ${PLATFORM_NAME}-web
ports:
- "8080:80"
volumes:
- ../../../src:/var/www/html
depends_on:
- fpm
networks:
- network
fpm:
build:
context: ../mtt-php82-nginx
dockerfile: Dockerfile-fpm
image: mtt-dev/php:8.2-fpm-alpine
container_name: ${PLATFORM_NAME}-fpm
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=sqlite
- MTT_API_USE_PATH_INFO=YES
volumes:
- ../../../src:/var/www/html
- ../../../tests:/var/www/tests
- ../mtt-php82-nginx/php-mtt.ini:/usr/local/etc/php/conf.d/php-mtt.ini
- ../mtt-php82-nginx/php-opcache.ini:/usr/local/etc/php/conf.d/php-opcache.ini
networks:
- network

View file

@ -0,0 +1 @@
PLATFORM_NAME=mtt-dev-php82-nginx-alpine

View file

@ -0,0 +1,15 @@
FROM php:8.2-fpm-alpine
RUN docker-php-ext-install pdo_mysql && \
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" && \
mkdir /var/www/phpinfo && \
echo "<?php phpinfo();" > /var/www/phpinfo/phpinfo.php && \
curl -o /usr/local/bin/phpunit -fL "https://phar.phpunit.de/phpunit-10.phar" && \
chmod +x /usr/local/bin/phpunit && \
ln -s html /var/www/src
#COPY php-mtt.ini /usr/local/etc/php/conf.d/
#COPY php-opcache.ini /usr/local/etc/php/conf.d/
COPY php-fpm-www.conf /usr/local/etc/php-fpm.d/www.conf
VOLUME /var/www/html

View file

@ -8,8 +8,8 @@ services:
web:
build:
context: ../_nginx
dockerfile: Dockerfile-nginx
image: mtt-dev/nginx
dockerfile: Dockerfile-nginx-alpine
image: mtt-dev/nginx:alpine
container_name: ${PLATFORM_NAME}-web
ports:
- "8080:80"
@ -24,12 +24,12 @@ services:
build:
context: .
dockerfile: Dockerfile-fpm
image: mtt-dev/php:8.1-fpm
image: mtt-dev/php:8.2-fpm-alpine
container_name: ${PLATFORM_NAME}-fpm
hostname: php-fpm
environment:
- MTT_ENABLE_DEBUG=YES
- MTT_DB_TYPE=sqlite
- MTT_API_USE_PATH_INFO=YES
volumes:
- ../../../src:/var/www/html
- ./php-mtt.ini:/usr/local/etc/php/conf.d/php-mtt.ini

View file

@ -0,0 +1,12 @@
[www]
user = www-data
group = www-data
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 500
access.format = "%R - %u [%t] \"%m %r\" %s %f"

View file

@ -0,0 +1,10 @@

; Since PHP 5.4 E_STRICT is included in E_ALL
error_reporting = E_ALL
log_errors = On
display_errors = On
display_startup_errors = On
cgi.fix_pathinfo=0
session.gc_divisor = 100

View file

@ -0,0 +1,42 @@
;; opcache.ini
;; check /usr/local/etc/php/conf.d if exists docker-php-ext-opcache.ini
; To disable opcache just comment this line
zend_extension=opcache.so
[opcache]
opcache.enable=1
opcache.enable_cli=1
; JIT will work only if opcache is enabled and jit_buffer_size is not zero.
; To disable JIT set the buffer size to 0.
opcache.jit_buffer_size=64M
; JIT mode. Read more at https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit
; Default is tracing.
opcache.jit=tracing
; The size of the shared memory storage used by OPcache, in megabytes. The minimum permissible value is "8", which is enforced if a smaller value is set.
; Default is 128.
opcache.memory_consumption=128
; The maximum number of keys (and therefore scripts) in the OPcache hash table.
; The actual value used will be the first number in the set of prime numbers
; { 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793 }
; that is greater than or equal to the configured value.
; The minimum value is 200. The maximum value is 1000000. Values outside of this range are clamped to the permissible range.
; Default is 10000.
opcache.max_accelerated_files=1000
; How often to check script timestamps for updates, in seconds. 0 will result in OPcache checking for updates on every request.
; This configuration directive is ignored if opcache.validate_timestamps is disabled.
; Default is 2.
opcache.revalidate_freq=2
; If enabled, OPcache will check for updated scripts every opcache.revalidate_freq seconds.
; When this directive is disabled, you must reset OPcache manually via opcache_reset(), opcache_invalidate()
; or by restarting the Web server for changes to the filesystem to take effect.
; Default is 1.
;; Override in docker-compose
opcache.validate_timestamps=1

View file

@ -2,14 +2,14 @@
// Rename it to config.php before using in docker.
if (getenv('MTT_DB_TYPE') == 'mysql') {
define("MTT_DB_TYPE", "mysql");
define("MTT_DB_HOST", getenv('MTT_DB_HOST'));
define("MTT_DB_NAME", getenv('MTT_DB_NAME'));
define("MTT_DB_USER", getenv('MTT_DB_USER'));
define("MTT_DB_PASSWORD", getenv('MTT_DB_PASSWORD'));
define("MTT_DB_PREFIX", getenv('MTT_DB_PREFIX'));
define("MTT_DB_DRIVER", getenv('MTT_DB_DRIVER'));
if (getenv('MTT_DB_TYPE') == 'mysql' || getenv('MTT_DB_TYPE') == 'postgres') {
define("MTT_DB_TYPE", getenv('MTT_DB_TYPE'));
define("MTT_DB_HOST", getenv('MTT_DB_HOST') ?: "undefined_host");
define("MTT_DB_NAME", getenv('MTT_DB_NAME') ?: "undefined_db");
define("MTT_DB_USER", getenv('MTT_DB_USER') ?: "undefined_user");
define("MTT_DB_PASSWORD", getenv('MTT_DB_PASSWORD') ?: "");
define("MTT_DB_PREFIX", getenv('MTT_DB_PREFIX') ?: "mtt_");
define("MTT_DB_DRIVER", getenv('MTT_DB_DRIVER') ?: "");
}
else if (getenv('MTT_DB_TYPE') == 'sqlite') {
define("MTT_DB_TYPE", "sqlite");
@ -17,3 +17,7 @@ else if (getenv('MTT_DB_TYPE') == 'sqlite') {
}
define("MTT_SALT", "Put Random Text Here");
//define("MTT_DISABLE_EXT", 1);
if (getenv('MTT_API_USE_PATH_INFO')) {
define("MTT_API_USE_PATH_INFO", 1);
}

View file

@ -24,22 +24,14 @@ if (!$listData) {
die("No list found.");
}
$sqlSort = "ORDER BY compl ASC, ";
if($listData['sorting'] == 1) $sqlSort .= "prio DESC, ddn ASC, duedate ASC, ow ASC";
elseif($listData['sorting'] == 2) $sqlSort .= "ddn ASC, duedate ASC, prio DESC, ow ASC";
else $sqlSort .= "ow ASC";
$data = DBCore::default()->getTasksByListId($listId, '', (int)$listData['sorting']);
$data = array();
$q = $db->dq("SELECT *, duedate IS NULL AS ddn FROM {$db->prefix}todolist WHERE list_id=$listId $sqlSort");
while($r = $q->fetchAssoc())
{
$data[] = $r;
if (_get('format') == 'ical') {
printICal($listData, $data);
}
else {
printCSV($listData, $data);
}
$format = _get('format');
if($format == 'ical') printICal($listData, $data);
else printCSV($listData, $data);
function printCSV(array $listData, array $data)

View file

@ -32,7 +32,7 @@ $feedType = _get('feed');
if($feedType == 'completed') {
$listData['_feed_descr'] = $lang->get('feed_completed_tasks');
fillData( $data, $listId, 'd_completed', 'AND compl=1' );
fillData( $data, $listId, 'd_completed', 'compl=1' );
}
elseif($feedType == 'modified') {
$listData['_feed_descr'] = $lang->get('feed_modified_tasks');
@ -40,13 +40,13 @@ elseif($feedType == 'modified') {
}
elseif($feedType == 'current') {
$listData['_feed_descr'] = $lang->get('feed_new_tasks');
fillData( $data, $listId, 'd_created', 'AND compl=0' );
fillData( $data, $listId, 'd_created', 'compl=0' );
}
elseif($feedType == 'status') {
$listData['_feed_descr'] = $lang->get('feed_tasks');
fillData( $data, $listId, 'd_created', '' );
fillData( $data, $listId, 'd_edited', 'AND compl=0 AND d_edited > d_created' );
fillData( $data, $listId, 'd_completed', 'AND compl=1' );
fillData( $data, $listId, 'd_edited', 'compl=0 AND d_edited > d_created' );
fillData( $data, $listId, 'd_completed', 'compl=1' );
}
else {
$listData['_feed_descr'] = $lang->get('feed_new_tasks');
@ -64,15 +64,14 @@ printRss($data, $listData);
function fillData(array &$data, int $listId, string $field, string $sqlWhere )
{
$tasks = DBCore::default()->getTasksByListId($listId, $sqlWhere, "$field DESC", 100);
$lang = Lang::instance();
$db = DBConnection::instance();
$q = $db->dq("SELECT * FROM {$db->prefix}todolist WHERE list_id=$listId $sqlWhere ORDER BY $field DESC LIMIT 100");
while ($r = $q->fetchAssoc())
foreach ($tasks as $r)
{
if ($r['prio'] > 0) {
$r['prio'] = '+'.$r['prio'];
}
$a = array();
$a = array(); //for _descr
$a[] = $lang->get('task'). ": ". $r['title'];
if ($r['prio']) {
$a[] = $lang->get('priority'). ": $r[prio]";

View file

@ -19,11 +19,11 @@ class TagsController extends ApiController {
checkReadAccess($listId);
$db = DBConnection::instance();
$sqlWhere = ($listId == -1) ? "" : "WHERE list_id=$listId";
$q = $db->dq("SELECT name,tag_id,COUNT(tag_id) AS tags_count
FROM {$db->prefix}tag2task INNER JOIN {$db->prefix}tags ON tag_id=id
$sqlWhere = ($listId == -1) ? "" : "WHERE list_id = $listId";
$q = $db->dq("SELECT name, tag_id, COUNT(tag_id) AS tags_count
FROM {$db->prefix}tag2task INNER JOIN {$db->prefix}tags ON tag_id = id
$sqlWhere
GROUP BY (tag_id)
GROUP BY tag_id, name
ORDER BY tags_count DESC");
$at = array();
$ac = array();
@ -72,10 +72,12 @@ class TagsController extends ApiController {
$db = DBConnection::instance();
$begin = trim(_get('q'));
$limit = 8;
$q = $db->dq("SELECT name,id FROM {$db->prefix}tags
$q = $db->dq("SELECT name, tag_id AS id FROM {$db->prefix}tags
INNER JOIN {$db->prefix}tag2task ON id=tag_id
WHERE list_id=$listId AND ". $db->like('name', '%s%%', $begin). "
GROUP BY tag_id ORDER BY name LIMIT $limit");
GROUP BY tag_id, name
ORDER BY name
LIMIT $limit");
$t = array();
while ($r = $q->fetchRow()) {
$t[] = $r[0];

View file

@ -22,14 +22,15 @@ class TasksController extends ApiController {
checkReadAccess($listId);
$db = DBConnection::instance();
$sqlWhere = $inner = $sqlWhereListId = $sqlInnerWhereListId = '';
$sqlWhere = $sqlWhereListId = $sqlInnerWhereListId = '';
if ($listId == -1) {
$userLists = $this->getUserListsSimple();
$sqlWhereListId = "{$db->prefix}todolist.list_id IN (". implode(',', array_keys($userLists)). ") ";
$sqlInnerWhereListId = "list_id IN (". implode(',', array_keys($userLists)). ") ";
$userListsIds = implode(',', array_keys($userLists));
$sqlWhereListId = "todo.list_id IN ($userListsIds) ";
$sqlInnerWhereListId = "list_id IN ($userListsIds) ";
}
else {
$sqlWhereListId = "{$db->prefix}todolist.list_id=". $listId;
$sqlWhereListId = "todo.list_id=". $listId;
$sqlInnerWhereListId = "list_id=$listId ";
}
if (_get('compl') == 0) {
@ -52,20 +53,18 @@ class TasksController extends ApiController {
}
}
// Include tags: All
if (sizeof($tagIds) > 1) {
$inner .= "INNER JOIN (SELECT task_id, COUNT(tag_id) AS c FROM {$db->prefix}tag2task WHERE $sqlInnerWhereListId AND tag_id IN (".
implode(',',$tagIds). ") GROUP BY task_id) AS t2t ON id=t2t.task_id";
$sqlWhere .= " AND c=". sizeof($tagIds);
}
elseif ($tagIds) {
$inner .= "INNER JOIN {$db->prefix}tag2task ON id=task_id";
$sqlWhere .= " AND tag_id = {$tagIds[0]}";
// Include tags
if ($tagIds) {
$sqlWhere .= " AND todo.id IN (SELECT task_id FROM {$db->prefix}tag2task WHERE tag_id IN (". implode(',',$tagIds). ")";
if (count($tagIds) > 1) {
$sqlWhere .= " GROUP BY task_id HAVING COUNT(tag_id)=". count($tagIds);
}
$sqlWhere .= ")";
}
// Exclude tags
if (sizeof($tagExIds) > 0) {
$sqlWhere .= " AND {$db->prefix}todolist.id NOT IN (SELECT DISTINCT task_id FROM {$db->prefix}tag2task WHERE $sqlInnerWhereListId AND tag_id IN (".
if (count($tagExIds) > 0) {
$sqlWhere .= " AND todo.id NOT IN (SELECT DISTINCT task_id FROM {$db->prefix}tag2task WHERE $sqlInnerWhereListId AND tag_id IN (".
implode(',',$tagExIds). "))";
}
//no optimization for single exTag
@ -74,7 +73,7 @@ class TasksController extends ApiController {
$s = trim(_get('s'));
if ($s != '') {
if (preg_match("|^#(\d+)$|", $s, $m)) {
$sqlWhere .= " AND {$db->prefix}todolist.id = ". (int)$m[1];
$sqlWhere .= " AND todo.id = ". (int)$m[1];
}
else {
$sqlWhere .= " AND (". $db->like("title", "%%%s%%", $s). " OR ". $db->like("note", "%%%s%%", $s). ")";
@ -98,7 +97,24 @@ class TasksController extends ApiController {
$t = array();
$t['total'] = 0;
$t['list'] = array();
$q = $db->dq("SELECT *, duedate IS NULL AS ddn FROM {$db->prefix}todolist $inner WHERE $sqlWhereListId $sqlWhere $sqlSort");
$groupConcat = '';
if ($db::DBTYPE == DBConnection::DBTYPE_POSTGRES) {
$groupConcat = "array_to_string(array_agg(tags.id), ',') AS tags_ids, string_agg(tags.name, ',') AS tags";
}
else {
$groupConcat = "GROUP_CONCAT(tags.id) AS tags_ids, GROUP_CONCAT(tags.name) AS tags";
}
$q = $db->dq("
SELECT todo.*, todo.duedate IS NULL AS ddn, $groupConcat
FROM {$db->prefix}todolist AS todo
LEFT JOIN {$db->prefix}tag2task AS t2t ON todo.id = t2t.task_id
LEFT JOIN {$db->prefix}tags AS tags ON t2t.tag_id = tags.id
WHERE $sqlWhereListId $sqlWhere
GROUP BY todo.id
$sqlSort
");
while ($r = $q->fetchAssoc())
{
$t['total']++;
@ -183,6 +199,11 @@ class TasksController extends ApiController {
checkWriteAccess();
$id = (int)$id;
if (!DBCore::default()->taskExists($id)) {
$this->response->data = ['total' => 0];
return;
}
$action = $this->req->jsonBody['action'] ?? '';
switch ($action) {
case 'edit': $this->response->data = $this->editTask($id); break;
@ -254,14 +275,12 @@ class TasksController extends ApiController {
$aTags = $this->prepareTags($tags);
if ($aTags) {
$this->addTaskTags($id, $aTags['ids'], $listId);
$db->ex("UPDATE {$db->prefix}todolist SET tags=?,tags_ids=? WHERE id=$id", array(implode(',',$aTags['tags']), implode(',',$aTags['ids'])));
}
}
$db->ex("COMMIT");
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
$oo = $this->prepareTaskRow($r);
MTTNotificationCenter::postNotification(MTTNotification::didCreateTask, $oo);
$t['list'][] = $oo;
$task = $this->getTaskRowById($id);
MTTNotificationCenter::postNotification(MTTNotification::didCreateTask, $task);
$t['list'][] = $task;
$t['total'] = 1;
return $t;
}
@ -294,14 +313,12 @@ class TasksController extends ApiController {
$aTags = $this->prepareTags($tags);
if ($aTags) {
$this->addTaskTags($id, $aTags['ids'], $listId);
$db->ex("UPDATE {$db->prefix}todolist SET tags=?,tags_ids=? WHERE id=$id", array(implode(',',$aTags['tags']), implode(',',$aTags['ids'])));
}
}
$db->ex("COMMIT");
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
$oo = $this->prepareTaskRow($r);
MTTNotificationCenter::postNotification(MTTNotification::didCreateTask, $oo);
$t['list'][] = $oo;
$task = $this->getTaskRowById($id);
MTTNotificationCenter::postNotification(MTTNotification::didCreateTask, $task);
$t['list'][] = $task;
$t['total'] = 1;
return $t;
}
@ -325,31 +342,26 @@ class TasksController extends ApiController {
$db->ex("BEGIN");
$db->ex("DELETE FROM {$db->prefix}tag2task WHERE task_id=$id");
$aTags = $this->prepareTags($tags);
if($aTags) {
$tags = implode(',', $aTags['tags']);
$tags_ids = implode(',',$aTags['ids']);
if ($aTags) {
$this->addTaskTags($id, $aTags['ids'], $listId);
}
$db->dq("UPDATE {$db->prefix}todolist SET title=?,note=?,prio=?,tags=?,tags_ids=?,duedate=?,d_edited=? WHERE id=$id",
array($title, $note, $prio, $tags, $tags_ids, $duedate, time()) );
$db->dq("UPDATE {$db->prefix}todolist SET title=?,note=?,prio=?,duedate=?,d_edited=? WHERE id=$id",
array($title, $note, $prio, $duedate, time()) );
$db->ex("COMMIT");
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
if ($r) {
$t['list'][] = $this->prepareTaskRow($r);
$t['total'] = 1;
}
$task = $this->getTaskRowById($id);
$t['list'][] = $task;
$t['total'] = 1;
return $t;
}
private function moveTask(int $id): ?array
{
$db = DBConnection::instance();
$fromId = (int)($this->req->jsonBody['from'] ?? 0);
$toId = (int)($this->req->jsonBody['to'] ?? 0);
$result = $this->doMoveTask($id, $toId);
$t = array('total' => $result ? 1 : 0);
if ($fromId == -1 && $result && $r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id")) {
$t['list'][] = $this->prepareTaskRow($r);
if ($fromId == -1 && $result) {
$t['list'][] = $this->getTaskRowById($id);
}
return $t;
}
@ -388,8 +400,7 @@ class TasksController extends ApiController {
array($dateCompleted, $date) );
$t = array();
$t['total'] = 1;
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
$t['list'][] = $this->prepareTaskRow($r);
$t['list'][] = $this->getTaskRowById($id);
return $t;
}
@ -456,6 +467,15 @@ class TasksController extends ApiController {
return $a;
}
private function getTaskRowById(int $id): ?array
{
$r = DBCore::default()->getTaskById($id);
if (!$r) {
throw new Exception("Failed to fetch task data");
}
return $this->prepareTaskRow($r);
}
private function prepareTaskRow(array $r): array
{
$lang = Lang::instance();
@ -483,7 +503,7 @@ class TasksController extends ApiController {
'date' => htmlarray($dCreated),
'dateInt' => (int)$r['d_created'],
'dateFull' => htmlarray($dCreatedFull),
'dateInlineTitle' => htmlarray(sprintf($lang->get('taskdate_inline_created'), $dCreated)),
'dateInlineTitle' => htmlarray(sprintf($lang->get('taskdate_inline_created'), $dCreated)), //TODO: move preparing of *inlineTitle to js
'dateEdited' => htmlarray($dEdited),
'dateEditedInt' => (int)$r['d_edited'],
'dateEditedFull' => htmlarray($dEditedFull),
@ -497,8 +517,8 @@ class TasksController extends ApiController {
'note' => noteMarkup($r['note']),
'noteText' => (string)$r['note'],
'ow' => (int)$r['ow'],
'tags' => htmlarray($r['tags']),
'tags_ids' => htmlarray($r['tags_ids']),
'tags' => htmlarray($r['tags'] ?? ''),
'tags_ids' => htmlarray($r['tags_ids'] ?? ''),
'duedate' => htmlarray($dueA['formatted']),
'dueClass' => $dueA['class'],
'dueStr' => htmlarray($dueA['str']),

View file

@ -0,0 +1,208 @@
<?php declare(strict_types=1);
/*
This file is a part of myTinyTodo.
(C) Copyright 2023 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
// ---------------------------------------------------------------------------- //
class DatabaseResult_Postgres extends DatabaseResult_Abstract
{
/** @var PDOStatement */
protected $q;
/** @var int */
protected $affected;
/** @var string const */
protected $schema = 'public';
function __construct(PDO $dbh, string $query, bool $resultless = false)
{
// use with DELETE, INSERT, UPDATE
if ($resultless)
{
$this->affected = (int) $dbh->exec($query); //throws PDOException
}
// SELECT
else
{
$this->q = $dbh->query($query); //throws PDOException
$this->affected = $this->q->rowCount();
}
}
function fetchRow(): ?array
{
$res = $this->q->fetch(PDO::FETCH_NUM);
if ($res === false || !is_array($res)) {
return null;
}
return $res;
}
function fetchAssoc(): ?array
{
$res = $this->q->fetch(PDO::FETCH_ASSOC);
if ($res === false || !is_array($res)) {
return null;
}
return $res;
}
function rowsAffected(): int
{
return $this->affected;
}
}
// ---------------------------------------------------------------------------- //
class Database_Postgres extends Database_Abstract
{
const DBTYPE = 'postgres';
/** @var PDO */
protected $dbh;
/** @var int */
protected $affected = 0;
protected $dbname;
function __construct()
{
}
function connect(array $params): void
{
$host = $params['host'];
$user = $params['user'];
$pass = $params['password'];
$db = $params['db'];
$options = array(
PDO::PGSQL_ATTR_DISABLE_PREPARES => 1,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
$this->dbname = $db;
$this->dbh = new PDO("pgsql:host=$host;dbname=$db", $user, $pass, $options);
}
/*
Returns single row of SELECT query as indexed array (FETCH_NUM).
Returns single field value if resulting array has only one field.
*/
function sq(string $query, ?array $values = null)
{
$q = $this->_dq($query, $values);
$res = $q->fetchRow();
if ($res === false || !is_array($res)) {
return null;
}
if (sizeof($res) > 1) return $res;
else return $res[0];
}
/*
Returns single row of SELECT query as dictionary array (FETCH_ASSOC).
*/
function sqa(string $query, ?array $values = null): ?array
{
$q = $this->_dq($query, $values);
$res = $q->fetchAssoc();
if ($res === false || !is_array($res)){
return null;
}
return $res;
}
function dq(string $query, ?array $values = null) : DatabaseResult_Abstract
{
return $this->_dq($query, $values);
}
/*
for resultless queries like INSERT,UPDATE,DELETE
*/
function ex(string $query, ?array $values = null): void
{
$this->_dq($query, $values, true);
}
private function _dq(string $query, ?array $values = null, bool $resultless = false) : DatabaseResult_Abstract
{
if (null !== $values && sizeof($values) > 0)
{
$m = explode('?', $query);
if (sizeof($m) < sizeof($values)+1) {
throw new Exception("params to set MORE than query params");
}
if (sizeof($m) > sizeof($values)+1) {
throw new Exception("params to set LESS than query params");
}
$query = "";
for ($i=0; $i<sizeof($m)-1; $i++) {
$query .= $m[$i]. $this->quote($values[$i]);
}
$query .= $m[$i];
}
$this->lastQuery = $query;
$dbr = new DatabaseResult_Postgres($this->dbh, $query, $resultless);
$this->affected = $dbr->rowsAffected();
return $dbr;
}
function affected(): int
{
return $this->affected;
}
function quote($value): string
{
if (null === $value) {
return 'null';
}
return $this->dbh->quote((string) $value);
}
function quoteForLike(string $format, string $string): string
{
$string = str_replace(array('\\','%','_'), array('\\\\','\%','\_'), $string);
return $this->dbh->quote(sprintf($format, $string)). " ESCAPE '\'";
}
function like(string $column, string $format, string $string): string
{
$column = str_replace('"', '""', $column);
return '"'. $column. '" ILIKE '. $this->quoteForLike($format, $string);
}
function lastInsertId(?string $name = null): ?string
{
$ret = $this->dbh->lastInsertId();
if (false === $ret) {
return null;
}
return (string) $ret;
}
function tableExists(string $table): bool
{
$r = $this->sq("SELECT 1 FROM information_schema.tables WHERE table_catalog = ? AND table_name = ?",
array($this->dbname, $table) );
if ($r === false || $r === null) return false;
return true;
}
function tableFieldExists(string $table, string $field): bool
{
$r = $this->sq("SELECT 1 FROM information_schema.columns WHERE table_name = ? AND column_name = ? AND table_schema = ?",
array($table, $field, $this->schema) );
if ($r === false || $r === null) return false;
return true;
}
}

View file

@ -62,8 +62,16 @@ class Database_Sqlite3 extends Database_Abstract
/** @var int */
protected $affected = 0;
function __construct()
/** @var bool */
protected $useNormalizedUtf8 = true;
function __construct(array $params = null)
{
if (is_array($params)) {
if (isset($params['useNormalizedUtf8'])) {
$this->useNormalizedUtf8 = boolval($params['useNormalizedUtf8']);
}
}
}
function connect(array $params): void
@ -73,8 +81,10 @@ class Database_Sqlite3 extends Database_Abstract
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
$this->dbh = new PDO("sqlite:$filename", null, null, $options); //throws PDOException
$this->dbh->sqliteCreateFunction('utf8_lower', [$this, 'utf8lower'], 1);
$this->dbh->sqliteCreateCollation('UTF8CI', [$this, 'utf8ci']);
$this->dbh->sqliteCreateFunction('utf8_lower', [$this, 'utf8_lower'], 1);
$this->dbh->sqliteCreateFunction('utf8_normalized_lower', [$this, 'utf8_normalized_lower'], 1);
$this->dbh->sqliteCreateCollation('UTF8CI', [$this, 'collate_utf8ci']);
$this->dbh->sqliteCreateCollation('UTF8CI_NORMALIZED', [$this, 'collate_utf8ci_normalized']);
}
/*
@ -170,7 +180,10 @@ class Database_Sqlite3 extends Database_Abstract
function like(string $column, string $format, string $string): string
{
$column = str_replace('"', '""', $column);
return 'utf8_lower("'. $column. '") LIKE '. $this->quoteForLike($format, mb_strtolower($string, 'UTF-8'));
if ($this->useNormalizedUtf8) {
return 'utf8_normalized_lower("'. $column. '") LIKE '. $this->quoteForLike($format, $this->utf8_normalized_lower($string));
}
return 'utf8_lower("'. $column. '") LIKE '. $this->quoteForLike($format, $this->utf8_lower($string));
}
function lastInsertId(?string $name = null): ?string
@ -204,14 +217,102 @@ class Database_Sqlite3 extends Database_Abstract
return false;
}
public function utf8lower($value)
public function utf8_lower($value): string
{
if (is_null($value)) return '';
return mb_strtolower((string)$value, 'UTF-8');
}
public function utf8ci(string $str1, string $str2): int
public function utf8_normalized_lower($value): string
{
if (is_null($value)) return '';
$value = self::normalizeValue((string) $value);
return mb_strtolower($value, 'UTF-8');
}
public function collate_utf8ci(string $str1, string $str2): int
{
return strcmp(mb_strtolower($str1, 'UTF-8'), mb_strtolower($str2, 'UTF-8'));
}
public function collate_utf8ci_normalized(string $str1, string $str2): int
{
$str1 = self::normalizeValue($str1);
$str2 = self::normalizeValue($str2);
return strcmp(mb_strtolower($str1, 'UTF-8'), mb_strtolower($str2, 'UTF-8'));
}
public static function normalizeValue(string $str): string
{
$str = Normalizer::normalize($str, Normalizer::FORM_KD);
if (false === preg_match_all("/./u", $str, $m)) {
$ea = error_get_last();
$error = ($ea && isset($ea['message'])) ? $ea['message'] : "preg_match_all() failed";
throw new Exception($error);
}
$chars = $m[0];
static $map = [
// https://en.wikipedia.org/wiki/Combining_character
"\u{0300}" => '', "\u{0301}" => '', "\u{0302}" => '', "\u{0303}" => '', "\u{0304}" => '', "\u{0305}" => '', "\u{0306}" => '', "\u{0307}" => '',
"\u{0308}" => '', "\u{0309}" => '', "\u{030a}" => '', "\u{030b}" => '', "\u{030c}" => '', "\u{030d}" => '', "\u{030e}" => '', "\u{030f}" => '',
"\u{0310}" => '', "\u{0311}" => '', "\u{0312}" => '', "\u{0313}" => '', "\u{0314}" => '', "\u{0315}" => '', "\u{0316}" => '', "\u{0317}" => '',
"\u{0318}" => '', "\u{0319}" => '', "\u{031a}" => '', "\u{031b}" => '', "\u{031c}" => '', "\u{031d}" => '', "\u{031e}" => '', "\u{031f}" => '',
"\u{0320}" => '', "\u{0321}" => '', "\u{0322}" => '', "\u{0323}" => '', "\u{0324}" => '', "\u{0325}" => '', "\u{0326}" => '', "\u{0327}" => '',
"\u{0328}" => '', "\u{0329}" => '', "\u{032a}" => '', "\u{032b}" => '', "\u{032c}" => '', "\u{032d}" => '', "\u{032e}" => '', "\u{032f}" => '',
"\u{0330}" => '', "\u{0331}" => '', "\u{0332}" => '', "\u{0333}" => '', "\u{0334}" => '', "\u{0335}" => '', "\u{0336}" => '', "\u{0337}" => '',
"\u{0338}" => '', "\u{0339}" => '', "\u{033a}" => '', "\u{033b}" => '', "\u{033c}" => '', "\u{033d}" => '', "\u{033e}" => '', "\u{033f}" => '',
"\u{0340}" => '', "\u{0341}" => '', "\u{0342}" => '', "\u{0343}" => '', "\u{0344}" => '', "\u{0345}" => '', "\u{0346}" => '', "\u{0347}" => '',
"\u{0348}" => '', "\u{0349}" => '', "\u{034a}" => '', "\u{034b}" => '', "\u{034c}" => '', "\u{034d}" => '', "\u{034e}" => '', "\u{034f}" => '',
"\u{0350}" => '', "\u{0351}" => '', "\u{0352}" => '', "\u{0353}" => '', "\u{0354}" => '', "\u{0355}" => '', "\u{0356}" => '', "\u{0357}" => '',
"\u{0358}" => '', "\u{0359}" => '', "\u{035a}" => '', "\u{035b}" => '', "\u{035c}" => '', "\u{035d}" => '', "\u{035e}" => '', "\u{035f}" => '',
"\u{0360}" => '', "\u{0361}" => '', "\u{0362}" => '', "\u{0363}" => '', "\u{0364}" => '', "\u{0365}" => '', "\u{0366}" => '', "\u{0367}" => '',
"\u{0368}" => '', "\u{0369}" => '', "\u{036a}" => '', "\u{036b}" => '', "\u{036c}" => '', "\u{036d}" => '', "\u{036e}" => '', "\u{036f}" => '',
"\u{1ab0}" => '', "\u{1ab1}" => '', "\u{1ab2}" => '', "\u{1ab3}" => '', "\u{1ab4}" => '', "\u{1ab5}" => '', "\u{1ab6}" => '', "\u{1ab7}" => '',
"\u{1ab8}" => '', "\u{1ab9}" => '', "\u{1aba}" => '', "\u{1abb}" => '', "\u{1abc}" => '', "\u{1abd}" => '', "\u{1abe}" => '', "\u{1abf}" => '',
"\u{1ac0}" => '', "\u{1ac1}" => '', "\u{1ac2}" => '', "\u{1ac3}" => '', "\u{1ac4}" => '', "\u{1ac5}" => '', "\u{1ac6}" => '', "\u{1ac7}" => '',
"\u{1ac8}" => '', "\u{1ac9}" => '', "\u{1aca}" => '', "\u{1acb}" => '', "\u{1acc}" => '', "\u{1acd}" => '', "\u{1ace}" => '', "\u{1acf}" => '',
"\u{1ad0}" => '', "\u{1ad1}" => '', "\u{1ad2}" => '', "\u{1ad3}" => '', "\u{1ad4}" => '', "\u{1ad5}" => '', "\u{1ad6}" => '', "\u{1ad7}" => '',
"\u{1ad8}" => '', "\u{1ad9}" => '', "\u{1ada}" => '', "\u{1adb}" => '', "\u{1adc}" => '', "\u{1add}" => '', "\u{1ade}" => '', "\u{1adf}" => '',
"\u{1ae0}" => '', "\u{1ae1}" => '', "\u{1ae2}" => '', "\u{1ae3}" => '', "\u{1ae4}" => '', "\u{1ae5}" => '', "\u{1ae6}" => '', "\u{1ae7}" => '',
"\u{1ae8}" => '', "\u{1ae9}" => '', "\u{1aea}" => '', "\u{1aeb}" => '', "\u{1aec}" => '', "\u{1aed}" => '', "\u{1aee}" => '', "\u{1aef}" => '',
"\u{1af0}" => '', "\u{1af1}" => '', "\u{1af2}" => '', "\u{1af3}" => '', "\u{1af4}" => '', "\u{1af5}" => '', "\u{1af6}" => '', "\u{1af7}" => '',
"\u{1af8}" => '', "\u{1af9}" => '', "\u{1afa}" => '', "\u{1afb}" => '', "\u{1afc}" => '', "\u{1afd}" => '', "\u{1afe}" => '', "\u{1aff}" => '',
"\u{1dc0}" => '', "\u{1dc1}" => '', "\u{1dc2}" => '', "\u{1dc3}" => '', "\u{1dc4}" => '', "\u{1dc5}" => '', "\u{1dc6}" => '', "\u{1dc7}" => '',
"\u{1dc8}" => '', "\u{1dc9}" => '', "\u{1dca}" => '', "\u{1dcb}" => '', "\u{1dcc}" => '', "\u{1dcd}" => '', "\u{1dce}" => '', "\u{1dcf}" => '',
"\u{1dd0}" => '', "\u{1dd1}" => '', "\u{1dd2}" => '', "\u{1dd3}" => '', "\u{1dd4}" => '', "\u{1dd5}" => '', "\u{1dd6}" => '', "\u{1dd7}" => '',
"\u{1dd8}" => '', "\u{1dd9}" => '', "\u{1dda}" => '', "\u{1ddb}" => '', "\u{1ddc}" => '', "\u{1ddd}" => '', "\u{1dde}" => '', "\u{1ddf}" => '',
"\u{1de0}" => '', "\u{1de1}" => '', "\u{1de2}" => '', "\u{1de3}" => '', "\u{1de4}" => '', "\u{1de5}" => '', "\u{1de6}" => '', "\u{1de7}" => '',
"\u{1de8}" => '', "\u{1de9}" => '', "\u{1dea}" => '', "\u{1deb}" => '', "\u{1dec}" => '', "\u{1ded}" => '', "\u{1dee}" => '', "\u{1def}" => '',
"\u{1df0}" => '', "\u{1df1}" => '', "\u{1df2}" => '', "\u{1df3}" => '', "\u{1df4}" => '', "\u{1df5}" => '', "\u{1df6}" => '', "\u{1df7}" => '',
"\u{1df8}" => '', "\u{1df9}" => '', "\u{1dfa}" => '', "\u{1dfb}" => '', "\u{1dfc}" => '', "\u{1dfd}" => '', "\u{1dfe}" => '', "\u{1dff}" => '',
"\u{20d0}" => '', "\u{20d1}" => '', "\u{20d2}" => '', "\u{20d3}" => '', "\u{20d4}" => '', "\u{20d5}" => '', "\u{20d6}" => '', "\u{20d7}" => '',
"\u{20d8}" => '', "\u{20d9}" => '', "\u{20da}" => '', "\u{20db}" => '', "\u{20dc}" => '', "\u{20dd}" => '', "\u{20de}" => '', "\u{20df}" => '',
"\u{20e0}" => '', "\u{20e1}" => '', "\u{20e2}" => '', "\u{20e3}" => '', "\u{20e4}" => '', "\u{20e5}" => '', "\u{20e6}" => '', "\u{20e7}" => '',
"\u{20e8}" => '', "\u{20e9}" => '', "\u{20ea}" => '', "\u{20eb}" => '', "\u{20ec}" => '', "\u{20ed}" => '', "\u{20ee}" => '', "\u{20ef}" => '',
"\u{20f0}" => '', "\u{20f1}" => '', "\u{20f2}" => '', "\u{20f3}" => '', "\u{20f4}" => '', "\u{20f5}" => '', "\u{20f6}" => '', "\u{20f7}" => '',
"\u{20f8}" => '', "\u{20f9}" => '', "\u{20fa}" => '', "\u{20fb}" => '', "\u{20fc}" => '', "\u{20fd}" => '', "\u{20fe}" => '', "\u{20ff}" => '',
"\u{fe20}" => '', "\u{fe21}" => '', "\u{fe22}" => '', "\u{fe23}" => '', "\u{fe24}" => '', "\u{fe25}" => '', "\u{fe26}" => '', "\u{fe27}" => '',
"\u{fe28}" => '', "\u{fe29}" => '', "\u{fe2a}" => '', "\u{fe2b}" => '', "\u{fe2c}" => '', "\u{fe2d}" => '', "\u{fe2e}" => '', "\u{fe2f}" => '',
'Æ' => 'AE', // "U+00c6"
'æ' => 'ae', // "U+00e6"
'Œ' => 'OE', // "U+0152"
'œ' => 'oe', // "U+0153"
];
$len = count($chars);
for ($i = 0; $i < $len; $i++) {
$unichar = $chars[$i];
if (isset($map[$unichar])) {
$chars[$i] = $map[$unichar];
}
}
return implode('', $chars);
}
}

View file

@ -8,6 +8,10 @@
class DBConnection
{
const DBTYPE_SQLITE = "sqlite";
const DBTYPE_MYSQL = "mysql";
const DBTYPE_POSTGRES = "postgres";
protected static $instance;
public static function init(Database_Abstract $instance) : Database_Abstract
@ -33,6 +37,7 @@ class DBConnection
abstract class Database_Abstract
{
const DBTYPE = '';
protected static $readonlyProps = ['prefix', 'lastQuery'];
/** @var string */

View file

@ -44,7 +44,7 @@ class DBCore
* @return DBCore
* @throws Exception
*/
public static function defaultInstance() : DBCore
public static function default() : DBCore
{
if (!isset(self::$defaultInstance)) {
throw new Exception("DBCore defaultInstance is not initialized");
@ -74,5 +74,92 @@ class DBCore
return $listId;
}
public function taskExists(int $id): bool
{
$db = $this->db;
$count = (int) $db->sq("SELECT COUNT(*) FROM {$db->prefix}todolist WHERE id = $id");
return ($count > 0) ? true : false;
}
public function getTaskById(int $id): ?array
{
$db = $this->db;
$groupConcat = '';
if ($db::DBTYPE == DBConnection::DBTYPE_POSTGRES) {
$groupConcat = "array_to_string(array_agg(tags.id), ',') AS tags_ids, string_agg(tags.name, ',') AS tags";
}
else {
$groupConcat = "GROUP_CONCAT(tags.id) AS tags_ids, GROUP_CONCAT(tags.name) AS tags";
}
$r = $db->sqa("
SELECT todo.*, $groupConcat
FROM {$db->prefix}todolist AS todo
LEFT JOIN {$db->prefix}tag2task AS t2t ON todo.id = t2t.task_id
LEFT JOIN {$db->prefix}tags AS tags ON t2t.tag_id = tags.id
WHERE todo.id = $id
GROUP BY todo.id
");
return $r;
}
/**
*
* @param int $listId
* @param string $sqlWhere
* @param int|string $sort
* @param null|int $limit
* @return array
*/
public function getTasksByListId(int $listId, string $sqlWhere, /* int|string */ $sort, ?int $limit = null): array
{
$db = $this->db;
if ($sqlWhere != '') {
$sqlWhere = "AND $sqlWhere";
}
$sqlSort = '';
if (is_int($sort)) {
$sqlSort = "ORDER BY compl ASC, ";
if ($sort == 1) $sqlSort .= "prio DESC, ddn ASC, duedate ASC, ow ASC"; // byPrio
elseif ($sort == 101) $sqlSort .= "prio ASC, ddn DESC, duedate DESC, ow DESC"; // byPrio (reverse)
elseif ($sort == 2) $sqlSort .= "ddn ASC, duedate ASC, prio DESC, ow ASC"; // byDueDate
elseif ($sort == 102) $sqlSort .= "ddn DESC, duedate DESC, prio ASC, ow DESC"; // byDueDate (reverse)
elseif ($sort == 3) $sqlSort .= "d_created ASC, prio DESC, ow ASC"; // byDateCreated
elseif ($sort == 103) $sqlSort .= "d_created DESC, prio ASC, ow DESC"; // byDateCreated (reverse)
elseif ($sort == 4) $sqlSort .= "d_edited ASC, prio DESC, ow ASC"; // byDateModified
elseif ($sort == 104) $sqlSort .= "d_edited DESC, prio ASC, ow DESC"; // byDateModified (reverse)
elseif ($sort == 5) $sqlSort .= "title ASC, prio DESC, ow ASC"; // byTitle
elseif ($sort == 105) $sqlSort .= "title DESC, prio ASC, ow DESC"; // byTitle (reverse)
else $sqlSort .= "ow ASC";
}
else if ($sort != '') {
$sqlSort = "ORDER BY $sort";
}
$sqlLimit = '';
if (!is_null($limit)) {
$sqlLimit = "LIMIT $limit";
}
$q = $db->dq("
SELECT todo.*, todo.duedate IS NULL AS ddn, GROUP_CONCAT(tags.id) AS tags_ids, GROUP_CONCAT(tags.name) AS tags
FROM {$db->prefix}todolist AS todo
LEFT JOIN {$db->prefix}tag2task AS t2t ON todo.id = t2t.task_id
LEFT JOIN {$db->prefix}tags AS tags ON t2t.tag_id = tags.id
WHERE todo.list_id = $listId $sqlWhere
GROUP BY todo.id
$sqlSort
$sqlLimit
");
$data = array();
while ($r = $q->fetchAssoc()) {
$data[] = $r;
}
return $data;
}
}

View file

@ -14,7 +14,7 @@ class ApiRequest
public $jsonBody;
function __construct() {
if (defined('MTT_API_USE_PATH_INFO') && MTT_API_USE_PATH_INFO) {
if (defined('MTT_API_USE_PATH_INFO')) {
$this->path = $_SERVER['PATH_INFO'];
}
else {

View file

@ -4,6 +4,6 @@ namespace mytinytodo;
class Version
{
const VERSION = '1.7.4';
const DB_VERSION = '1.7';
const VERSION = '1.8-dev';
const DB_VERSION = '1.8';
}

View file

@ -49,7 +49,7 @@ function parseRoute($queryString)
redirectWithHashRoute($hash, $q);
}
else if (isset($q['task'])) {
$listId = (int)DBCore::defaultInstance()->getListIdByTaskId((int)$q['task']);
$listId = (int)DBCore::default()->getListIdByTaskId((int)$q['task']);
if ($listId > 0) {
$h = [ 'list', $listId, 'search', '#'. (int)$q['task']];
redirectWithHashRoute($h);

View file

@ -103,12 +103,12 @@ function configureDbConnection()
}
DBConnection::init($db);
try {
$db->connect( array(
$db->connect([
'host' => MTT_DB_HOST,
'user' => MTT_DB_USER,
'password' => MTT_DB_PASSWORD,
'db' => MTT_DB_NAME,
));
]);
}
catch(Exception $e) {
logAndDie("Failed to connect to mysql database: ". $e->getMessage());
@ -116,12 +116,40 @@ function configureDbConnection()
$db->dq("SET NAMES utf8mb4");
}
# PostgreSQL Database
else if (MTT_DB_TYPE == 'postgres')
{
require_once(MTTINC. 'class.db.postgres.php');
$db = DBConnection::init(new Database_Postgres());
try {
$db->connect([
'host' => MTT_DB_HOST,
'user' => MTT_DB_USER,
'password' => MTT_DB_PASSWORD,
'db' => MTT_DB_NAME,
]);
}
catch(Exception $e) {
$errlog = "Failed to connect to PostgreSQL database: ". $e->getMessage();
if (MTT_DEBUG) {
logAndDie($errlog);
}
else {
logAndDie("Failed to connect to database", $errlog);
}
}
$db->dq("SET NAMES 'utf8'");
}
# SQLite3 Database
elseif (MTT_DB_TYPE == 'sqlite')
{
require_once(MTTINC. 'vendor/autoload.php');
require_once(MTTINC. 'class.db.sqlite3.php');
$db = DBConnection::init(new Database_Sqlite3);
$db->connect( array( 'filename' => MTTPATH. 'db/todolist.db' ) );
$db = DBConnection::init(new Database_Sqlite3());
$db->connect([
'filename' => MTTPATH. 'db/todolist.db'
]);
}
else {
die("Incorrect database connection config");
@ -346,7 +374,12 @@ function get_unsafe_mttinfo($v)
/* URL for API, like http://localhost/mytinytodo/api/. No need to set by default. */
$_mttinfo['api_url'] = Config::getUrl('api_url'); // need to have a trailing slash
if ($_mttinfo['api_url'] == '') {
$_mttinfo['api_url'] = get_unsafe_mttinfo('mtt_uri'). 'api.php?_path=/';
if (defined('MTT_API_USE_PATH_INFO')) {
$_mttinfo['api_url'] = get_unsafe_mttinfo('mtt_uri'). 'api/';
}
else {
$_mttinfo['api_url'] = get_unsafe_mttinfo('mtt_uri'). 'api.php?_path=/';
}
}
return $_mttinfo['api_url'];
case 'title':

View file

@ -7,7 +7,7 @@
*/
// Can be used to upgrade database from myTinyTodo v1.4 or later
$lastVer = '1.7';
$lastVer = '1.8';
if (version_compare(PHP_VERSION, '7.2.0') < 0) {
die("PHP 7.2 or above is required");
@ -77,22 +77,28 @@ if ($configExists)
// Determine current installed db version
$ver = databaseVersion($db);
if ($ver == '1.4') {
if ($ver == '') {
// clean install
// will not load settings from database in init.php
Config::$noDatabase = true;
}
else if (version_compare($ver, '1.4') < 0) {
// Very old or previously failed while install
exitMessage(htmlspecialchars("Can not update. Unsupported database version ($ver)."));
}
else if ($ver == '1.4') {
// Need to upgrade. Do not ask for old password
require_once(MTTPATH. 'db/config.php');
Config::loadConfigV14($config);
unset($config);
DBConnection::init($db);
}
else {
if ($ver != '1.7') {
Config::$noDatabase = true; //will not load settings from database in init.php
}
require_once('./init.php');
if ( !is_logged() ) {
die("Access denied!<br> Disable password protection or Log in.");
}
require_once('./init.php');
if ( !is_logged() ) {
die("Access denied!<br> Disable password protection or Log in.");
}
}
if ($ver == '')
@ -102,7 +108,7 @@ if ($ver == '')
if ($install == '' && $db !== null)
{
# We already have settings file and need to create tables.
exitMessage("<form method=post>Click next to create tables in '". htmlspecialchars($dbtype). "' database.<br><br>
exitMessage("<form method=post>Click next to create tables in ". htmlspecialchars(databaseTypeName($db)). " database.<br><br>
<input type=hidden name=stoken value='$setupToken'>
<input type=hidden name=install value=create>
<input type=submit value=' Next '></form>");
@ -114,9 +120,10 @@ if ($ver == '')
<form method=post>Select database type to use:<br><br>
<input type=hidden name=install value=config>
<input type=hidden name=stoken value='$setupToken'>
<label><input type=radio name=db_type value=sqlite checked=checked onclick=\"document.getElementById('mysqlsettings').style.display='none'\">SQLite</label><br><br>
<label><input type=radio name=db_type value=mysql onclick=\"document.getElementById('mysqlsettings').style.display=''\">MySQL</label><br>
<div id='mysqlsettings' style='display:none; margin-left:30px;'><br><table>
<label><input type=radio name=db_type value=sqlite checked=checked onclick=\"document.getElementById('dbsettings').style.display='none'\"> SQLite</label><br><br>
<label><input type=radio name=db_type value=mysql onclick=\"document.getElementById('dbsettings').style.display=''\"> MySQL</label><br><br>
<label><input type=radio name=db_type value=postgres onclick=\"document.getElementById('dbsettings').style.display=''\"> PostgreSQL (beta)</label><br>
<div id='dbsettings' style='display:none; margin-left:30px;'><br><table>
<tr><td>Host:</td><td><input name=db_host value=localhost></td></tr>
<tr><td>Database:</td><td><input name=db_name value=mytinytodo></td></tr>
<tr><td>User:</td><td><input name=db_user value=mtt></td></tr>
@ -129,9 +136,12 @@ if ($ver == '')
{
check_post_stoken();
# Save configuration
$dbtype = ($_POST['db_type'] == 'mysql') ? 'mysql' : 'sqlite';
$dbtype = $_POST['db_type'] ?? '';
if ($dbtype != 'mysql' && $dbtype != 'postgres' && $dbtype != 'sqlite') {
exitMessage("Unknown database type $dbtype");
}
Config::set('db.type', $dbtype);
if ($dbtype == 'mysql') {
if ($dbtype == 'mysql' || $dbtype == 'postgres') {
Config::set('db.host', _post('db_host'));
Config::set('db.name', _post('db_name'));
Config::set('db.user', _post('db_user'));
@ -177,7 +187,7 @@ elseif ($ver == $lastVer)
}
else
{
if (!in_array($ver, array('1.4'))) {
if (!in_array($ver, array('1.4','1.7'))) {
exitMessage(htmlspecialchars("Can not update. Unsupported database version ($ver)."));
}
@ -192,9 +202,12 @@ else
# update process
check_post_stoken();
if ($ver == '1.4')
{
if ($ver == '1.4') {
update_14_17($db, $dbtype);
update_17_18($db, $dbtype);
}
elseif ($ver == '1.7') {
update_17_18($db, $dbtype);
}
}
@ -237,12 +250,15 @@ function createAllTables($db, $dbtype)
if ($dbtype == 'mysql') {
createMysqlTables($db);
}
else if ($dbtype == 'postgres') {
createPostgresTables($db);
}
else {
createSqliteTables($db);
}
}
/* ===== mysql ========================================================= */
function createMysqlTables($db)
{
$db->ex(
@ -275,8 +291,6 @@ function createMysqlTables($db)
`note` TEXT,
`prio` TINYINT NOT NULL default 0, /* priority -,0,+ */
`ow` INT NOT NULL default 0, /* order weight */
`tags` VARCHAR(600) NOT NULL default '', /* for fast access to task tags */
`tags_ids` VARCHAR(250) NOT NULL default '', /* no more than 22 tags (x11 chars) */
`duedate` DATE default NULL,
PRIMARY KEY(`id`),
KEY(`list_id`),
@ -287,7 +301,7 @@ function createMysqlTables($db)
$db->ex(
"CREATE TABLE {$db->prefix}tags (
`id` INT UNSIGNED NOT NULL auto_increment,
`name` VARCHAR(50) NOT NULL,
`name` VARCHAR(250) NOT NULL default '',
PRIMARY KEY(`id`),
UNIQUE KEY `name` (`name`)
) CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci ");
@ -323,6 +337,78 @@ function createMysqlTables($db)
}
/* ===== postgres =============================================== */
function createPostgresTables($db)
{
$db->ex(
"CREATE TABLE {$db->prefix}lists (
id INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
uuid CHAR(36) NOT NULL default '',
ow INTEGER NOT NULL default 0,
name VARCHAR(250) NOT NULL default '',
d_created INTEGER NOT NULL default 0,
d_edited INTEGER NOT NULL default 0,
sorting SMALLINT NOT NULL default 0,
published SMALLINT NOT NULL default 0,
taskview INTEGER NOT NULL default 0,
extra TEXT
) ");
$db->ex("CREATE UNIQUE INDEX {$db->prefix}lists_uuid ON {$db->prefix}lists (uuid)");
$db->ex(
"CREATE TABLE {$db->prefix}todolist (
id INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
uuid CHAR(36) NOT NULL default '',
list_id INTEGER NOT NULL default 0,
d_created INTEGER NOT NULL default 0,
d_completed INTEGER NOT NULL default 0,
d_edited INTEGER NOT NULL default 0,
compl SMALLINT NOT NULL default 0,
title VARCHAR(250) NOT NULL default '',
note TEXT default NULL,
prio SMALLINT NOT NULL default 0,
ow INTEGER NOT NULL default 0,
duedate DATE default NULL
) ");
$db->ex("CREATE INDEX {$db->prefix}todo_list_id ON {$db->prefix}todolist (list_id)");
$db->ex("CREATE UNIQUE INDEX {$db->prefix}todo_uuid ON {$db->prefix}todolist (uuid)");
$db->ex(
"CREATE TABLE {$db->prefix}tags (
id INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(250) NOT NULL DEFAULT ''
) ");
$db->ex("CREATE UNIQUE INDEX {$db->prefix}tags_name ON {$db->prefix}tags (name)");
$db->ex(
"CREATE TABLE {$db->prefix}tag2task (
tag_id INTEGER NOT NULL,
task_id INTEGER NOT NULL,
list_id INTEGER NOT NULL
) ");
$db->ex("CREATE INDEX {$db->prefix}tag2task_tag_id ON {$db->prefix}tag2task (tag_id)");
$db->ex("CREATE INDEX {$db->prefix}tag2task_task_id ON {$db->prefix}tag2task (task_id)");
$db->ex("CREATE INDEX {$db->prefix}tag2task_list_id ON {$db->prefix}tag2task (list_id)");
$db->ex(
"CREATE TABLE {$db->prefix}settings (
param_key VARCHAR(250) NOT NULL default '',
param_value TEXT
) ");
$db->ex("CREATE UNIQUE INDEX {$db->prefix}settings_key ON {$db->prefix}settings (param_key)");
$db->ex(
"CREATE TABLE {$db->prefix}sessions (
id VARCHAR(64) NOT NULL default '',
data TEXT,
last_access INTEGER NOT NULL default 0,
expires INTEGER NOT NULL default 0
) ");
$db->ex("CREATE UNIQUE INDEX {$db->prefix}sessions_id ON {$db->prefix}sessions (id)");
}
/* ===== sqlite =============================================== */
function createSqliteTables($db)
{
$db->ex(
@ -330,7 +416,7 @@ function createSqliteTables($db)
id INTEGER PRIMARY KEY,
uuid CHAR(36) NOT NULL,
ow INTEGER NOT NULL default 0,
name VARCHAR(50) NOT NULL,
name VARCHAR(250) NOT NULL,
d_created INTEGER UNSIGNED NOT NULL default 0,
d_edited INTEGER UNSIGNED NOT NULL default 0,
sorting TINYINT UNSIGNED NOT NULL default 0,
@ -344,18 +430,16 @@ function createSqliteTables($db)
$db->ex(
"CREATE TABLE {$db->prefix}todolist (
id INTEGER PRIMARY KEY,
uuid CHAR(36) NOT NULL,
uuid CHAR(36) NOT NULL default '',
list_id INTEGER UNSIGNED NOT NULL default 0,
d_created INTEGER UNSIGNED NOT NULL default 0,
d_completed INTEGER UNSIGNED NOT NULL default 0,
d_edited INTEGER UNSIGNED NOT NULL default 0,
compl TINYINT UNSIGNED NOT NULL default 0,
title VARCHAR(250) NOT NULL,
note TEXT,
title VARCHAR(250) NOT NULL default '' COLLATE UTF8CI,
note TEXT COLLATE UTF8CI default NULL,
prio TINYINT NOT NULL default 0,
ow INTEGER NOT NULL default 0,
tags VARCHAR(600) NOT NULL default '',
tags_ids VARCHAR(250) NOT NULL default '',
duedate DATE default NULL
) ");
$db->ex("CREATE INDEX todo_list_id ON {$db->prefix}todolist (list_id)");
@ -365,9 +449,9 @@ function createSqliteTables($db)
$db->ex(
"CREATE TABLE {$db->prefix}tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(50) NOT NULL COLLATE NOCASE
name VARCHAR(250) NOT NULL DEFAULT '' COLLATE UTF8CI
) ");
$db->ex("CREATE UNIQUE INDEX tags_name ON {$db->prefix}tags (name COLLATE NOCASE)");
$db->ex("CREATE INDEX tags_name ON {$db->prefix}tags (name)"); //NB: unique in mysql
$db->ex(
@ -419,6 +503,8 @@ function databaseVersion(Database_Abstract $db): string
$v = '1.4';
if ( !$db->tableExists($db->prefix.'settings') ) return $v;
$v = '1.7';
if ( $db->tableFieldExists($db->prefix.'todolist', 'tags') ) return $v;
$v = '1.8';
return $v;
}
@ -514,12 +600,31 @@ function testConnect(&$error)
if (!defined($c)) throw new Exception("$c is not defined");
}
$db->connect( array(
$db->connect([
'host' => MTT_DB_HOST,
'user' => MTT_DB_USER,
'password' => MTT_DB_PASSWORD,
'db' => MTT_DB_NAME
));
]);
}
else if (MTT_DB_TYPE == 'postgres')
{
if (!defined('PDO::PGSQL_ATTR_DISABLE_PREPARES')) {
throw new Exception("Required PHP extension 'PDO_PostgreSQL' is not installed.");
}
require_once(MTTINC. 'class.db.postgres.php');
foreach (['MTT_DB_HOST', 'MTT_DB_USER', 'MTT_DB_PASSWORD', 'MTT_DB_NAME', 'MTT_DB_PREFIX'] as $c) {
if (!defined($c)) throw new Exception("$c is not defined");
}
$db = new Database_Postgres;
$db->connect([
'host' => MTT_DB_HOST,
'user' => MTT_DB_USER,
'password' => MTT_DB_PASSWORD,
'db' => MTT_DB_NAME
]);
}
else if (MTT_DB_TYPE == 'sqlite')
{
@ -534,7 +639,9 @@ function testConnect(&$error)
}
require_once(MTTINC. 'class.db.sqlite3.php');
$db = new Database_Sqlite3;
$db->connect( array( 'filename' => MTTPATH. 'db/todolist.db' ) );
$db->connect([
'filename' => MTTPATH. 'db/todolist.db'
]);
}
else {
throw new Exception("Unsupported database type: ". MTT_DB_TYPE);
@ -565,6 +672,17 @@ function myExceptionHandler($e)
exit;
}
function databaseTypeName(Database_Abstract $db)
{
$s = get_class($db);
switch ($s) {
case "Database_Mysql": return "MySQL";
case "Database_Postgres": return "PostgreSQL";
case "Database_Sqlite3": return "SQLite";
default: return $s;
}
}
### update v1.4 to v1.7 ##########
function update_14_17(Database_Abstract $db, $dbtype)
@ -576,7 +694,7 @@ function update_14_17(Database_Abstract $db, $dbtype)
$db->ex("ALTER TABLE {$db->prefix}lists ADD `extra` TEXT");
# increase the length of list and tag name
# (not applicable to sqlite because it uses VARCHAR fields of eny length as TEXT)
# (not applicable to sqlite because it uses VARCHAR fields of any length as TEXT)
$db->ex("ALTER TABLE {$db->prefix}todolist CHANGE `tags` `tags` VARCHAR(2000) NOT NULL default '' ");
$db->ex("ALTER TABLE {$db->prefix}tags CHANGE `name` `name` VARCHAR(250) NOT NULL default '' ");
$db->ex("ALTER TABLE {$db->prefix}lists CHANGE `name` `name` VARCHAR(250) NOT NULL default '' ");
@ -642,3 +760,64 @@ UNIQUE KEY `id` (`id`)
Config::saveDbConfig();
}
### end of 1.7 #####
### update v1.7 to v1.8 ##########
function update_17_18(Database_Abstract $db, $dbtype)
{
$db->ex("BEGIN");
if ($dbtype == 'sqlite')
{
// Use UTF8CI collate. Old sqlite does not support DROP COLUMN (before v3.35.0 2021-03-12)
$db->ex("DROP INDEX todo_list_id");
$db->ex("DROP INDEX todo_uuid");
$db->ex("ALTER TABLE {$db->prefix}todolist RENAME TO {$db->prefix}todolist_old");
$db->ex(
"CREATE TABLE {$db->prefix}todolist (
id INTEGER PRIMARY KEY,
uuid CHAR(36) NOT NULL default '',
list_id INTEGER UNSIGNED NOT NULL default 0,
d_created INTEGER UNSIGNED NOT NULL default 0,
d_completed INTEGER UNSIGNED NOT NULL default 0,
d_edited INTEGER UNSIGNED NOT NULL default 0,
compl TINYINT UNSIGNED NOT NULL default 0,
title VARCHAR(250) NOT NULL default '' COLLATE UTF8CI,
note TEXT COLLATE UTF8CI default NULL,
prio TINYINT NOT NULL default 0,
ow INTEGER NOT NULL default 0,
duedate DATE default NULL )"
);
$db->ex("INSERT INTO {$db->prefix}todolist SELECT id,uuid,list_id,d_created,d_completed,d_edited,compl,title,note,prio,ow,duedate FROM {$db->prefix}todolist_old");
$db->ex("CREATE INDEX todo_list_id ON {$db->prefix}todolist (list_id)");
$db->ex("CREATE UNIQUE INDEX todo_uuid ON {$db->prefix}todolist (uuid)");
$db->ex("DROP TABLE {$db->prefix}todolist_old");
$db->ex("DROP INDEX tags_name");
$db->ex("ALTER TABLE {$db->prefix}tags RENAME TO {$db->prefix}tags_old");
$db->ex(
"CREATE TABLE {$db->prefix}tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(250) NOT NULL DEFAULT '' COLLATE UTF8CI )"
);
$db->ex("INSERT INTO {$db->prefix}tags SELECT * FROM {$db->prefix}tags_old");
$db->ex("CREATE INDEX tags_name ON {$db->prefix}tags (name)");
$db->ex("DROP TABLE {$db->prefix}tags_old");
}
else // mysql
{
$db->ex("ALTER TABLE {$db->prefix}todolist DROP COLUMN tags");
$db->ex("ALTER TABLE {$db->prefix}todolist DROP COLUMN tags_ids");
// if mysql db was created in v1.7.x then tags.name field has length of 50 instead of 250
$db->ex("ALTER TABLE {$db->prefix}tags CHANGE `name` `name` VARCHAR(250) NOT NULL default '' ");
}
$db->ex("COMMIT");
if ($dbtype == 'sqlite') {
$db->ex("VACUUM");
}
}
### end of 1.8 #####