diff --git a/composer.json b/composer.json index 83c8724..b4e1580 100644 --- a/composer.json +++ b/composer.json @@ -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" diff --git a/composer.lock b/composer.lock index 5ff3829..b36dc60 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "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": [], diff --git a/docker/dev/_nginx/Dockerfile-angie-alpine b/docker/dev/_nginx/Dockerfile-angie-alpine new file mode 100644 index 0000000..12250a6 --- /dev/null +++ b/docker/dev/_nginx/Dockerfile-angie-alpine @@ -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 diff --git a/docker/dev/_nginx/Dockerfile-nginx-alpine b/docker/dev/_nginx/Dockerfile-nginx-alpine new file mode 100644 index 0000000..1312e5b --- /dev/null +++ b/docker/dev/_nginx/Dockerfile-nginx-alpine @@ -0,0 +1,5 @@ +FROM nginx:alpine + +COPY default.conf /etc/nginx/conf.d/ + +VOLUME /var/www/html diff --git a/docker/dev/_nginx/default.conf b/docker/dev/_nginx/default.conf index deea342..d9c1b0b 100644 --- a/docker/dev/_nginx/default.conf +++ b/docker/dev/_nginx/default.conf @@ -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 { diff --git a/docker/dev/mtt-php72-angie-postgres10/.env b/docker/dev/mtt-php72-angie-postgres10/.env new file mode 100644 index 0000000..b32391f --- /dev/null +++ b/docker/dev/mtt-php72-angie-postgres10/.env @@ -0,0 +1 @@ +PLATFORM_NAME=mtt-dev-php72-angie-postgres10 diff --git a/docker/dev/mtt-php72-angie-postgres10/Dockerfile-fpm b/docker/dev/mtt-php72-angie-postgres10/Dockerfile-fpm new file mode 100644 index 0000000..edcf6d7 --- /dev/null +++ b/docker/dev/mtt-php72-angie-postgres10/Dockerfile-fpm @@ -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 " /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 diff --git a/docker/dev/mtt-php72-angie-postgres10/compose.yml b/docker/dev/mtt-php72-angie-postgres10/compose.yml new file mode 100644 index 0000000..88f91df --- /dev/null +++ b/docker/dev/mtt-php72-angie-postgres10/compose.yml @@ -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 diff --git a/docker/dev/mtt-php72-angie-postgres10/php-fpm-www.conf b/docker/dev/mtt-php72-angie-postgres10/php-fpm-www.conf new file mode 100644 index 0000000..b162e94 --- /dev/null +++ b/docker/dev/mtt-php72-angie-postgres10/php-fpm-www.conf @@ -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" diff --git a/docker/dev/mtt-php81-nginx/php-mtt.ini b/docker/dev/mtt-php72-angie-postgres10/php-mtt.ini similarity index 100% rename from docker/dev/mtt-php81-nginx/php-mtt.ini rename to docker/dev/mtt-php72-angie-postgres10/php-mtt.ini diff --git a/docker/dev/mtt-php81-nginx/php-opcache.ini b/docker/dev/mtt-php72-angie-postgres10/php-opcache.ini similarity index 100% rename from docker/dev/mtt-php81-nginx/php-opcache.ini rename to docker/dev/mtt-php72-angie-postgres10/php-opcache.ini diff --git a/docker/dev/mtt-php72-apache-postgres10/.env b/docker/dev/mtt-php72-apache-postgres10/.env new file mode 100644 index 0000000..927f4f3 --- /dev/null +++ b/docker/dev/mtt-php72-apache-postgres10/.env @@ -0,0 +1 @@ +PLATFORM_NAME=mtt-dev-php72-apache-postgres10 diff --git a/docker/dev/mtt-php72-apache-postgres10/compose.yml b/docker/dev/mtt-php72-apache-postgres10/compose.yml new file mode 100644 index 0000000..e1b0e71 --- /dev/null +++ b/docker/dev/mtt-php72-apache-postgres10/compose.yml @@ -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 diff --git a/docker/dev/mtt-php72-apache/Dockerfile-web b/docker/dev/mtt-php72-apache/Dockerfile-web index 8fd0e08..ee64439 100644 --- a/docker/dev/mtt-php72-apache/Dockerfile-web +++ b/docker/dev/mtt-php72-apache/Dockerfile-web @@ -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 " /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/ diff --git a/docker/dev/mtt-php74-nginx/compose.yml b/docker/dev/mtt-php74-nginx/compose.yml index d4d9cec..2186d16 100644 --- a/docker/dev/mtt-php74-nginx/compose.yml +++ b/docker/dev/mtt-php74-nginx/compose.yml @@ -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 diff --git a/docker/dev/mtt-php81-nginx/.env b/docker/dev/mtt-php81-nginx/.env deleted file mode 100644 index f2087fd..0000000 --- a/docker/dev/mtt-php81-nginx/.env +++ /dev/null @@ -1 +0,0 @@ -PLATFORM_NAME=mtt-dev-php81-nginx diff --git a/docker/dev/mtt-php81-nginx/Dockerfile-fpm b/docker/dev/mtt-php81-nginx/Dockerfile-fpm deleted file mode 100644 index fbfefe4..0000000 --- a/docker/dev/mtt-php81-nginx/Dockerfile-fpm +++ /dev/null @@ -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 " /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 diff --git a/docker/dev/mtt-php82-angie/.env b/docker/dev/mtt-php82-angie/.env new file mode 100644 index 0000000..0993892 --- /dev/null +++ b/docker/dev/mtt-php82-angie/.env @@ -0,0 +1 @@ +PLATFORM_NAME=mtt-dev-php82-angie-alpine diff --git a/docker/dev/mtt-php82-angie/compose.yml b/docker/dev/mtt-php82-angie/compose.yml new file mode 100644 index 0000000..a389e34 --- /dev/null +++ b/docker/dev/mtt-php82-angie/compose.yml @@ -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 diff --git a/docker/dev/mtt-php82-nginx/.env b/docker/dev/mtt-php82-nginx/.env new file mode 100644 index 0000000..aa353f2 --- /dev/null +++ b/docker/dev/mtt-php82-nginx/.env @@ -0,0 +1 @@ +PLATFORM_NAME=mtt-dev-php82-nginx-alpine diff --git a/docker/dev/mtt-php82-nginx/Dockerfile-fpm b/docker/dev/mtt-php82-nginx/Dockerfile-fpm new file mode 100644 index 0000000..29e5f3c --- /dev/null +++ b/docker/dev/mtt-php82-nginx/Dockerfile-fpm @@ -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 " /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 diff --git a/docker/dev/mtt-php81-nginx/compose.yml b/docker/dev/mtt-php82-nginx/compose.yml similarity index 82% rename from docker/dev/mtt-php81-nginx/compose.yml rename to docker/dev/mtt-php82-nginx/compose.yml index 7e8e41a..d810205 100644 --- a/docker/dev/mtt-php81-nginx/compose.yml +++ b/docker/dev/mtt-php82-nginx/compose.yml @@ -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 diff --git a/docker/dev/mtt-php82-nginx/php-fpm-www.conf b/docker/dev/mtt-php82-nginx/php-fpm-www.conf new file mode 100644 index 0000000..b162e94 --- /dev/null +++ b/docker/dev/mtt-php82-nginx/php-fpm-www.conf @@ -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" diff --git a/docker/dev/mtt-php82-nginx/php-mtt.ini b/docker/dev/mtt-php82-nginx/php-mtt.ini new file mode 100644 index 0000000..f9b6186 --- /dev/null +++ b/docker/dev/mtt-php82-nginx/php-mtt.ini @@ -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 diff --git a/docker/dev/mtt-php82-nginx/php-opcache.ini b/docker/dev/mtt-php82-nginx/php-opcache.ini new file mode 100644 index 0000000..2866d5c --- /dev/null +++ b/docker/dev/mtt-php82-nginx/php-opcache.ini @@ -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 diff --git a/src/docker-config.php b/src/docker-config.php index 0d2d954..8d6dd9d 100644 --- a/src/docker-config.php +++ b/src/docker-config.php @@ -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); +} diff --git a/src/export.php b/src/export.php index 4ff7d04..d3cdca9 100644 --- a/src/export.php +++ b/src/export.php @@ -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) diff --git a/src/feed.php b/src/feed.php index 0475fb1..369a461 100644 --- a/src/feed.php +++ b/src/feed.php @@ -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]"; diff --git a/src/includes/api/TagsController.php b/src/includes/api/TagsController.php index 814c1ae..8158ea8 100644 --- a/src/includes/api/TagsController.php +++ b/src/includes/api/TagsController.php @@ -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]; diff --git a/src/includes/api/TasksController.php b/src/includes/api/TasksController.php index cf8fded..9a0cbe8 100644 --- a/src/includes/api/TasksController.php +++ b/src/includes/api/TasksController.php @@ -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']), diff --git a/src/includes/class.db.postgres.php b/src/includes/class.db.postgres.php new file mode 100644 index 0000000..776c557 --- /dev/null +++ b/src/includes/class.db.postgres.php @@ -0,0 +1,208 @@ + + 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; $iquote($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; + } +} diff --git a/src/includes/class.db.sqlite3.php b/src/includes/class.db.sqlite3.php index 0abeaf2..348244c 100644 --- a/src/includes/class.db.sqlite3.php +++ b/src/includes/class.db.sqlite3.php @@ -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); + } + } diff --git a/src/includes/class.dbconnection.php b/src/includes/class.dbconnection.php index 606a79e..389510c 100644 --- a/src/includes/class.dbconnection.php +++ b/src/includes/class.dbconnection.php @@ -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 */ diff --git a/src/includes/class.dbcore.php b/src/includes/class.dbcore.php index 8aaa47f..7774310 100644 --- a/src/includes/class.dbcore.php +++ b/src/includes/class.dbcore.php @@ -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; + } } diff --git a/src/includes/classes.php b/src/includes/classes.php index 8907877..388f43d 100644 --- a/src/includes/classes.php +++ b/src/includes/classes.php @@ -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 { diff --git a/src/includes/version.php b/src/includes/version.php index 29bbd3e..a9cb109 100644 --- a/src/includes/version.php +++ b/src/includes/version.php @@ -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'; } diff --git a/src/index.php b/src/index.php index 6a68eea..a87bdaa 100644 --- a/src/index.php +++ b/src/index.php @@ -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); diff --git a/src/init.php b/src/init.php index 81971eb..9e01343 100644 --- a/src/init.php +++ b/src/init.php @@ -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': diff --git a/src/setup.php b/src/setup.php index 828c3d5..ea3bccc 100644 --- a/src/setup.php +++ b/src/setup.php @@ -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!
Disable password protection or Log in."); - } + + require_once('./init.php'); + if ( !is_logged() ) { + die("Access denied!
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("
Click next to create tables in '". htmlspecialchars($dbtype). "' database.

+ exitMessage("Click next to create tables in ". htmlspecialchars(databaseTypeName($db)). " database.

"); @@ -114,9 +120,10 @@ if ($ver == '')
Select database type to use:

-

-
-