├── src
├── ext
│ ├── index.html
│ ├── backup
│ │ ├── .htaccess
│ │ ├── extension.json
│ │ ├── lang
│ │ │ ├── en.json
│ │ │ └── ru.json
│ │ ├── class.download.php
│ │ ├── loader.php
│ │ ├── class.controller.php
│ │ └── class.check.php
│ ├── updater
│ │ ├── .htaccess
│ │ ├── extension.json
│ │ ├── lang
│ │ │ ├── en.json
│ │ │ ├── de.json
│ │ │ └── ru.json
│ │ ├── class.controller.php
│ │ ├── loader.php
│ │ └── class.updater.php
│ ├── CustomCSS
│ │ ├── .htaccess
│ │ ├── extension.json
│ │ ├── lang
│ │ │ ├── en.json
│ │ │ ├── ru.json
│ │ │ └── pl.json
│ │ └── loader.php
│ ├── notifications
│ │ ├── .htaccess
│ │ ├── extension.json
│ │ ├── cli-notify.php
│ │ ├── lang
│ │ │ ├── en.json
│ │ │ ├── ru.json
│ │ │ └── de.json
│ │ ├── class.observer.php
│ │ ├── class.controller.php
│ │ └── class.telegramapi.php
│ ├── _examples
│ │ └── CustomSmartSyntax
│ │ │ ├── .htaccess
│ │ │ ├── extension.json
│ │ │ └── loader.php
│ └── .htaccess
├── content
│ ├── index.html
│ ├── js
│ │ └── index.html
│ └── theme
│ │ ├── index.html
│ │ ├── images
│ │ ├── index.html
│ │ ├── logo.gif
│ │ ├── loading48.gif
│ │ ├── logo-loading.gif
│ │ ├── arr-left.svg
│ │ ├── arr-right.svg
│ │ ├── arrdown2.svg
│ │ ├── checkmark.svg
│ │ ├── select.svg
│ │ ├── select-dark.svg
│ │ ├── search.svg
│ │ ├── closetag.svg
│ │ ├── plus.svg
│ │ ├── selectlist.svg
│ │ ├── back.svg
│ │ ├── task-menu.svg
│ │ ├── note-toggle.svg
│ │ ├── add.svg
│ │ ├── selectlist2.svg
│ │ ├── task-menu2.svg
│ │ ├── newtask-ext.svg
│ │ ├── COPYRIGHT
│ │ ├── calendar.svg
│ │ ├── rss.svg
│ │ ├── search-cancel.svg
│ │ ├── rss-disabled.svg
│ │ └── svg2base64.php
│ │ ├── style_rtl.css
│ │ ├── print.css
│ │ ├── dark.css
│ │ └── markdown.css
├── includes
│ ├── index.html
│ ├── .htaccess
│ ├── version.php
│ ├── lang
│ │ ├── en-rtl.json
│ │ ├── readme.md
│ │ ├── _percent.php
│ │ ├── ja.json
│ │ ├── he.json
│ │ ├── sv.json
│ │ ├── th.json
│ │ ├── mk.json
│ │ ├── sl.json
│ │ ├── cz.json
│ │ ├── da.json
│ │ ├── it.json
│ │ ├── ar.json
│ │ ├── sk.json
│ │ ├── pt-pt.json
│ │ └── hu.json
│ ├── api
│ │ ├── AuthController.php
│ │ ├── TagsController.php
│ │ └── ExtSettingsController.php
│ ├── markup.commonmark.php
│ ├── filters.php
│ ├── markup.parsedown.php
│ ├── class.dbconnection.php
│ ├── markup.php
│ ├── notifications.php
│ ├── class.sessionhandler.php
│ └── class.db.mysqli.php
├── db
│ └── .htaccess
├── config-sample.php
├── .htaccess
├── mtt-emergency.php
├── docker-config.php
├── mtt-edit-settings.php
├── COPYRIGHT
├── index.php
└── feed.php
├── .gitignore
├── composer.sh
├── README.md
├── composer.json
└── .editorconfig
/src/ext/index.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/content/index.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/includes/index.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/content/js/index.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/content/theme/index.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/db/.htaccess:
--------------------------------------------------------------------------------
1 | deny from all
--------------------------------------------------------------------------------
/src/ext/backup/.htaccess:
--------------------------------------------------------------------------------
1 | deny from all
2 |
--------------------------------------------------------------------------------
/src/ext/updater/.htaccess:
--------------------------------------------------------------------------------
1 | deny from all
2 |
--------------------------------------------------------------------------------
/src/includes/.htaccess:
--------------------------------------------------------------------------------
1 | deny from all
2 |
--------------------------------------------------------------------------------
/src/ext/CustomCSS/.htaccess:
--------------------------------------------------------------------------------
1 | deny from all
2 |
--------------------------------------------------------------------------------
/src/content/theme/images/index.html:
--------------------------------------------------------------------------------
1 | Place for Images
--------------------------------------------------------------------------------
/src/ext/notifications/.htaccess:
--------------------------------------------------------------------------------
1 | deny from all
2 |
--------------------------------------------------------------------------------
/src/ext/_examples/CustomSmartSyntax/.htaccess:
--------------------------------------------------------------------------------
1 | deny from all
2 |
--------------------------------------------------------------------------------
/src/content/theme/images/logo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxpozdeev/mytinytodo/HEAD/src/content/theme/images/logo.gif
--------------------------------------------------------------------------------
/src/ext/.htaccess:
--------------------------------------------------------------------------------
1 |
2 | Order deny,allow
3 | Deny from all
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/content/theme/images/loading48.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxpozdeev/mytinytodo/HEAD/src/content/theme/images/loading48.gif
--------------------------------------------------------------------------------
/src/content/theme/images/logo-loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxpozdeev/mytinytodo/HEAD/src/content/theme/images/logo-loading.gif
--------------------------------------------------------------------------------
/src/ext/backup/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "bundleId": "backup",
3 | "name": "Backup",
4 | "version": "1.1",
5 | "description": "Backup"
6 | }
7 |
--------------------------------------------------------------------------------
/src/content/theme/images/arr-left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/content/theme/images/arr-right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/content/theme/images/arrdown2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ext/updater/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "bundleId": "updater",
3 | "name": "Updates",
4 | "version": "0.9.4",
5 | "description": "myTinyTodo self-updater"
6 | }
7 |
--------------------------------------------------------------------------------
/src/includes/version.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/content/theme/images/select.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/content/theme/images/select-dark.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | src/db/todolist.db*
3 | src/db/config.php
4 | src/db/config-*
5 | src/db/backup.xml*
6 | src/config.php
7 | src/includes/vendor/
8 | src/content/theme/custom.css
9 |
10 | tests/
11 |
--------------------------------------------------------------------------------
/src/ext/notifications/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "bundleId": "notifications",
3 | "name": "Notifications",
4 | "version": "1.2.1",
5 | "description": "Notify about new tasks and lists on e-mail or telegram"
6 | }
7 |
--------------------------------------------------------------------------------
/src/content/theme/images/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ext/_examples/CustomSmartSyntax/extension.json:
--------------------------------------------------------------------------------
1 | {
2 | "bundleId": "CustomSmartSyntax",
3 | "name": "Custom Smart Syntax",
4 | "version": "1.0",
5 | "description": "Prototype to write you own smart syntax parser"
6 | }
7 |
--------------------------------------------------------------------------------
/composer.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #dir="$( dirname -- "$( readlink -f -- "$0"; )"; )"
4 | dir="$PWD"
5 |
6 | app=$(which podman)
7 | if [ -z $app ]; then
8 | app="docker"
9 | fi
10 |
11 | $app run -it --rm -v "$dir:/app" composer $@
12 |
--------------------------------------------------------------------------------
/src/content/theme/images/closetag.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/content/theme/images/plus.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/content/theme/images/selectlist.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/content/theme/images/back.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/content/theme/images/task-menu.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/content/theme/images/note-toggle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/content/theme/images/add.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ext/CustomCSS/lang/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext.CustomCSS.name": "Custom CSS",
3 | "customcss.h_css": "CSS",
4 | "customcss.d_css": "Write you own CSS rules",
5 | "customcss.not_writable": "CSS file is not writable, check permissions for custom.css",
6 | "customcss.saved": "Saved"
7 | }
8 |
--------------------------------------------------------------------------------
/src/content/theme/images/selectlist2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/content/theme/images/task-menu2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ext/CustomCSS/lang/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext.CustomCSS.name": "Дополнительные стили",
3 | "customcss.h_css": "CSS",
4 | "customcss.d_css": "Добавляйте собственные стили CSS",
5 | "customcss.not_writable": "Ошибка записи в файл, проверьте права доступа к custom.css",
6 | "customcss.saved": "Сохранено"
7 | }
8 |
--------------------------------------------------------------------------------
/src/ext/CustomCSS/lang/pl.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext.CustomCSS.name": "Własny styl CSS",
3 | "customcss.h_css": "CSS",
4 | "customcss.d_css": "Dodaj własne reguły CSS",
5 | "customcss.not_writable": "Plik stylu nie jest zapisywalny, sprawdź uprawnienia dla pliku custom.css",
6 | "customcss.saved": "Zmiany zostały zapisane"
7 | }
8 |
--------------------------------------------------------------------------------
/src/includes/lang/en-rtl.json:
--------------------------------------------------------------------------------
1 | {
2 | "_header": {
3 | "ver": "v1.6",
4 | "date": "2020-09-04",
5 | "language": "English (for RTL test)",
6 | "original_name": "English RTL",
7 | "author": "Max Pozdeev",
8 | "author_url": "http://www.mytinytodo.net",
9 | "rtl": 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/content/theme/images/newtask-ext.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/content/theme/images/COPYRIGHT:
--------------------------------------------------------------------------------
1 | rss.svg, rss-disabled.svg - are (or based on) icons by Icons8 from https://icons8.com/icon/13841/rss
2 | calendar.svg - icon by Icons8 from https://icons8.com/icon/__LA9wZgJaqd/calendar
3 | loading48.gif - generated at Preloaders.net
4 |
5 | Other images in this directory were made by Max Pozdeev the author of myTinyTodo
6 | and licensed under the terms of GNU GPL version 2 or any later.
7 |
--------------------------------------------------------------------------------
/src/content/theme/images/calendar.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/content/theme/images/rss.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/content/theme/images/search-cancel.svg:
--------------------------------------------------------------------------------
1 |
4 |
11 |
--------------------------------------------------------------------------------
/src/content/theme/images/rss-disabled.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # myTinyTodo
2 |
3 | Your tiny todo list
4 |
5 | Original website - http://www.mytinytodo.net/
6 |
7 | ### System requirements
8 | - PHP 7.2 or greater
9 | - PHP extensions:
10 | - mbstring
11 | - pdo_sqlite, intl (SQLite version)
12 | - pdo_mysql or mysqli (MySQL version)
13 | - pdo_pgsql (PostgreSQL version)
14 | - One of databases:
15 | - MySQL 5.7 or greater / MariaDB 10.2 or greater
16 | - PostgreSQL 10 or greater
17 | - SQLite (system library)
18 |
19 | Supported browsers: Chrome 49, Safari 10, Firefox 53.
20 |
21 | Internet Explorer and Opera with Presto engine are not supported.
22 |
23 |
--------------------------------------------------------------------------------
/src/config-sample.php:
--------------------------------------------------------------------------------
1 | =7.2",
17 | "ext-mbstring": "*",
18 | "erusev/parsedown": "1.7.x-dev#f7285e7",
19 | "symfony/polyfill-intl-normalizer": "^1.31"
20 | },
21 | "require-dev": {
22 | "league/commonmark": "^2.6"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/.htaccess:
--------------------------------------------------------------------------------
1 | # For REST API in Apache
2 | #
3 | # RewriteEngine On
4 | # RewriteCond %{REQUEST_FILENAME} !-f
5 | # RewriteCond %{REQUEST_FILENAME} !-d
6 | # RewriteRule ^api/(.*)$ api.php/$1 [L,QSA]
7 | #
8 | #
9 | # Allow from all
10 | #
11 |
12 |
13 | # In Nginx set something like this:
14 |
15 | # Deny access to some files and folders
16 | #location ~ ^/(db|includes)/ {
17 | # deny all;
18 | #}
19 | #location ~ /\.ht {
20 | # deny all;
21 | #}
22 | #location ~* ^/ext/.*\.(json|md)$ {
23 | # deny all;
24 | #}
25 |
26 | # Optional
27 | # location /api/ {
28 | # rewrite ^/api/(.*) /api.php/$1 last;
29 | # }
30 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # top-most EditorConfig file
2 | root = true
3 |
4 | # 'insert_final_newline = false' should not remove last empty line
5 |
6 | [*]
7 | charset = utf-8
8 | end_of_line = lf
9 | # tab_width here is only used to represent tabs if indent_size is not set
10 | tab_width = 4
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 |
14 | [*.php]
15 | indent_style = space
16 | indent_size = 4
17 |
18 | [*.js]
19 | indent_style = space
20 | tab_width = 4
21 |
22 | [*.{css,yml,html,svg,xml}]
23 | indent_style = space
24 | indent_size = 2
25 |
26 | [*.md]
27 | trim_trailing_whitespace = false
28 | insert_final_newline = false
29 |
30 | [/src/includes/theme.php]
31 | indent_style = space
32 | indent_size = 2
33 |
--------------------------------------------------------------------------------
/src/mtt-emergency.php:
--------------------------------------------------------------------------------
1 | ");
19 | }
20 |
21 |
22 | function exitmsg(?string $text = '') {
23 | echo "
Password Reset
\n";
24 | echo $text;
25 | echo "
For security reasons delete this file after usage!";
26 | exit;
27 | }
28 |
--------------------------------------------------------------------------------
/src/ext/updater/lang/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext.updater.name": "Updates",
3 | "updater.urlconfigwarning": "Enable PHP 'allow_url_fopen' directive to be able to download updates.",
4 | "updater.tarwarning": "Update is not possible, 'tar' utility is not found.",
5 | "updater.h_check_updates": "Check updates",
6 | "updater.check": "Check",
7 | "updater.no_updates": "No updates",
8 | "updater.last_version": "Last available version: %s",
9 | "updater.current_version": "Current version",
10 | "updater.last_checked": "Last checked",
11 | "updater.new_version_available": "New version is available",
12 | "updater.update": "Update",
13 | "updater.updated": "Update has completed",
14 | "updater.download_error": "Download failed",
15 | "updater.update_error": "Update error"
16 | }
17 |
--------------------------------------------------------------------------------
/src/ext/updater/lang/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext.updater.name": "Updates",
3 | "updater.urlconfigwarning": "Aktivieren Sie die PHP-Anweisung 'allow_url_fopen', um Updates herunterladen zu können.",
4 | "updater.tarwarning": "Update nicht möglich, 'tar' Erweiterung nicht gefunden.",
5 | "updater.h_check_updates": "Checke Updates",
6 | "updater.check": "Check",
7 | "updater.no_updates": "Keine Updates",
8 | "updater.last_version": "Letzte verfügbare Version: %s",
9 | "updater.current_version": "Aktuelle Version",
10 | "updater.last_checked": "Letzter Check",
11 | "updater.new_version_available": "Neue Version verfügbar",
12 | "updater.update": "Update",
13 | "updater.updated": "Update komplett",
14 | "updater.download_error": "Download fehlgeschlagen",
15 | "updater.update_error": "Updatefehler"
16 | }
17 |
--------------------------------------------------------------------------------
/src/ext/updater/lang/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext.updater.name": "Обновления",
3 | "updater.urlconfigwarning": "Для получения обновлений требуется включить директиву 'allow_url_fopen' в настройках PHP .",
4 | "updater.tarwarning": "Исполняемый файл 'tar' не найден, обновление невозможно.",
5 | "updater.h_check_updates": "Проверка обновлений",
6 | "updater.check": "Проверить",
7 | "updater.no_updates": "Нет обновлений",
8 | "updater.last_version": "Актуальная версия: %s",
9 | "updater.current_version": "Текущая версия",
10 | "updater.last_checked": "Последняя проверка",
11 | "updater.new_version_available": "Доступно обновление",
12 | "updater.update": "Обновить",
13 | "updater.updated": "Обновление завершено",
14 | "updater.download_error": "Ошибка при загрузке",
15 | "updater.update_error": "Ошибка при обновлении"
16 | }
17 |
--------------------------------------------------------------------------------
/src/ext/backup/lang/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext.backup.name": "Backup",
3 | "backup.h_make": "Make backup",
4 | "backup.d_make": "Will create backup file in '%s' folder.",
5 | "backup.make": "Make",
6 | "backup.download": "Download",
7 | "backup.done": "Done",
8 | "backup.not_writable": "Backup file is not writable, check permissions for db/backup.xml",
9 | "backup.last_backup": "Last backup: %s",
10 | "backup.h_inconsistency": "Check for inconsistency",
11 | "backup.d_inconsistency": "If you want to restore the backup to another database version or type, it's better to fix inconsistency before making backup.",
12 | "backup.check": "Check",
13 | "backup.repair": "Repair",
14 | "backup.h_restore": "Restore from backup",
15 | "backup.d_restore": "Will delete all records in existing database before restoring.",
16 | "backup.restore": "Restore…"
17 | }
18 |
--------------------------------------------------------------------------------
/src/ext/backup/lang/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext.backup.name": "Резервное копирование",
3 | "backup.h_make": "Сделать копию",
4 | "backup.d_make": "Сохранит файл в папке '%s'.",
5 | "backup.make": "Создать",
6 | "backup.download": "Скачать",
7 | "backup.done": "Выполнено",
8 | "backup.not_writable": "Ошибка записи в файл, проверьте права доступа к db/backup.xml",
9 | "backup.last_backup": "Резервная копия: %s",
10 | "backup.h_inconsistency": "Проверка базы данных",
11 | "backup.d_inconsistency": "Если планируете восстановление из резервной копии в другую базу данных, то лучше исправить возможные ошибки перед резервным копированием.",
12 | "backup.check": "Проверить",
13 | "backup.repair": "Исправить",
14 | "backup.h_restore": "Восстановить из резервной копии",
15 | "backup.d_restore": "Удалит все существующие записи во время восстановления.",
16 | "backup.restore": "Восстановить…"
17 | }
18 |
--------------------------------------------------------------------------------
/src/docker-config.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | if (!defined('MTTPATH')) {
10 | die("Unexpected usage.");
11 | }
12 |
13 | function mtt_ext_customsmartsyntax_instance(): MTTExtension
14 | {
15 | return new CustomSmartSyntaxExtension();
16 | }
17 |
18 | class CustomSmartSyntaxExtension extends MTTExtension implements MTTFilterInterface
19 | {
20 | //the same as dir name
21 | const bundleId = 'CustomSmartSyntax';
22 |
23 | function init() {
24 | \MTTFilterCenter::addFilterForAction('parseSmartSyntax', $this);
25 | }
26 |
27 | // parseSmartSyntax
28 | function filter($title, &$out)
29 | {
30 | $a = [
31 | 'prio' => 0, // int: -1 .. 2
32 | 'title' => $title, // string
33 | 'tags' => '', // string: "tag1, tag2, tag3,..."
34 | 'duedate' => null, // string: "Y-m-d" format
35 | ];
36 |
37 | // This filter just overwrites results of default parser
38 |
39 | $out = $a;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/mtt-edit-settings.php:
--------------------------------------------------------------------------------
1 | \n".
9 | " mtt-edit-settings.php write \n".
10 | " mtt-edit-settings.php password \n"
11 | );
12 | }
13 |
14 | $dontStartSession = true;
15 | require_once(__DIR__ . '/init.php');
16 |
17 | $cmd = $argv[1];
18 | $param = $argv[2];
19 | $value = $argc > 3 ? $argv[3] : null;
20 |
21 |
22 | switch ($cmd) {
23 | case 'read': cmd_read($param, $value); break;
24 | case 'write': cmd_write($param, $value); break;
25 | case 'password': cmd_password($param); break;
26 | default: die("Unknown command: $cmd\n");
27 | }
28 |
29 |
30 | function cmd_read($param) {
31 | print Config::get($param) . "\n";
32 | }
33 |
34 | function cmd_write($param, $value) {
35 | if ($value === null) {
36 | die("Can not write '$param': value is not specified\n");
37 | }
38 | print ("Set '$param' to '$value'\n");
39 | Config::set($param, $value);
40 | Config::save();
41 | print ("Done!\n");
42 | }
43 |
44 | function cmd_password($value) {
45 | $value = passwordHash($value);
46 | cmd_write('password', $value);
47 | }
48 |
--------------------------------------------------------------------------------
/src/includes/lang/readme.md:
--------------------------------------------------------------------------------
1 | # myTinyTodo Translations
2 |
3 | | Locale | Lines | % Done |
4 | |:-------|--------:|-------:|
5 | | ar | 147/202 | 73% |
6 | | bg | 147/202 | 73% |
7 | | ca | 147/202 | 73% |
8 | | cz | 147/202 | 73% |
9 | | da | 147/202 | 73% |
10 | | de | 198/202 | 98% |
11 | | el | 147/202 | 73% |
12 | | en | 202/202 | 100% |
13 | | es | 147/202 | 73% |
14 | | es-mx | 147/202 | 73% |
15 | | et | 198/202 | 98% |
16 | | fa | 187/202 | 93% |
17 | | fr | 195/202 | 97% |
18 | | he | 147/202 | 73% |
19 | | hu | 147/202 | 73% |
20 | | it | 147/202 | 73% |
21 | | ja | 147/202 | 73% |
22 | | lt | 147/202 | 73% |
23 | | mk | 147/202 | 73% |
24 | | nl | 197/202 | 98% |
25 | | no | 147/202 | 73% |
26 | | pl | 175/202 | 87% |
27 | | pt-br | 187/202 | 93% |
28 | | pt-pt | 147/202 | 73% |
29 | | ro | 147/202 | 73% |
30 | | ru | 202/202 | 100% |
31 | | sk | 147/202 | 73% |
32 | | sl | 147/202 | 73% |
33 | | sr | 147/202 | 73% |
34 | | sv | 147/202 | 73% |
35 | | th | 147/202 | 73% |
36 | | tr | 147/202 | 73% |
37 | | uk | 147/202 | 73% |
38 | | vi | 147/202 | 73% |
39 | | zh-cn | 196/202 | 97% |
40 | | zh-tw | 147/202 | 73% |
41 |
--------------------------------------------------------------------------------
/src/content/theme/images/svg2base64.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | // call like: php -f svg2base64.php > ../images.css
10 |
11 | if (php_sapi_name() != 'cli') {
12 | error_log("Supports cli only");
13 | exit(-1);
14 | }
15 |
16 | if (isset($argv[1])) {
17 | print base64file($argv[1]);
18 | exit();
19 | }
20 |
21 | $files = [];
22 | $h = opendir(__DIR__);
23 | while ( false !== ($file = readdir($h)) )
24 | {
25 | if ( preg_match('/(.+)\.svg$/', $file, $m) ) {
26 | $files[] = $m[1];
27 | }
28 | }
29 | closedir($h);
30 |
31 | if (!$files) {
32 | exit;
33 | }
34 | sort($files);
35 |
36 | print ":root {\n";
37 | foreach ($files as $name) {
38 | $b64 = base64file(__DIR__. "/$name.svg");
39 | print " --svg-{$name}: url('data:image/svg+xml;base64,$b64');\n";
40 | }
41 | print "}\n";
42 |
43 | function base64file(string $filename): string
44 | {
45 | $content = file_get_contents($filename);
46 | //$content = str_replace(["\n","\r\n"], ['',''], $content);
47 | $content = cleanXml($content);
48 | return base64_encode($content);
49 | }
50 |
51 | function cleanXml(string $data): string
52 | {
53 | $dom = new DOMDocument;
54 | $dom->preserveWhiteSpace = false;
55 | $dom->loadXML($data);
56 |
57 | $xpath = new DOMXPath($dom);
58 | foreach ($xpath->query('//comment()') as $comment) {
59 | $comment->parentNode->removeChild($comment);
60 | }
61 | return $dom->saveXML();
62 | }
63 |
--------------------------------------------------------------------------------
/src/ext/notifications/cli-notify.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | set_time_limit(30);
10 |
11 | if (php_sapi_name() != 'cli') {
12 | error_log("Supports cli only");
13 | exit(-1);
14 | }
15 | if (!function_exists('pcntl_fork')) {
16 | error_log("Required PHP module is not found: pcntl");
17 | exit(-2);
18 | }
19 | if (!function_exists('posix_setsid')) {
20 | error_log("Required PHP module is not found: posix");
21 | exit(-2);
22 | }
23 | $dontStartSession = 1;
24 | require(__DIR__.'/../../init.php');
25 |
26 | $hash = fgets(STDIN);
27 | if ($hash === false) {
28 | error_log("No input");
29 | exit(-3);
30 | }
31 | $hash = trim($hash);
32 | $text = stream_get_contents(STDIN);
33 |
34 | // Wi will fork a child to do a long work
35 | $pid = pcntl_fork();
36 | if ($pid == -1) {
37 | error_log("Failed to fork a child");
38 | exit(-1);
39 | }
40 | else if ($pid) {
41 | // parent will not wait for child's exit
42 | exit;
43 | }
44 |
45 | // Child is here, detach it
46 | if (posix_setsid() < 0) {
47 | error_log("posix_setsid() failed");
48 | exit;
49 | }
50 |
51 | $prefs = NotificationsExtension::preferences();
52 | if (!isset($prefs['token'])) {
53 | error_log("No telegram token");
54 | exit(-4);
55 | }
56 | $token = $prefs['token'] ?? '';
57 | if (!password_verify($prefs['token'], $hash)) {
58 | error_log("Not authorized");
59 | exit(-5);
60 | }
61 |
62 | $sender = new Notify\Sender($prefs);
63 | $sender->sendTelegramsWithApi($text);
64 |
--------------------------------------------------------------------------------
/src/ext/notifications/lang/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext.notifications.name": "Notifications",
3 | "notifications.urlconfigwarning": "Enable PHP 'allow_url_fopen' directive to use Telegram notifications.",
4 | "notifications.check": "Check",
5 | "notifications.bot_not_configured": "Bot is not configured",
6 | "notifications.g_email": "E-Mail",
7 | "notifications.h_email": "E-mail:",
8 | "notifications.d_email": "Separate multiple addresses with comma.",
9 | "notifications.h_mailfrom": "Mail from:",
10 | "notifications.d_mailfrom": "Use this e-mail as sender's address.",
11 | "notifications.g_telegram": "Telegram",
12 | "notifications.h_token": "Bot token:",
13 | "notifications.d_token": "Telegram Bot API token from @BotFather.",
14 | "notifications.h_active_chats": "Active chats:",
15 | "notifications.d_active_chats": "Number of chats where bot sends notifications.",
16 | "notifications.deactivate_all": "Deactivate all",
17 | "notifications.h_new_chat": "New chat:",
18 | "notifications.d_new_chat": "Start new conversation with the bot, send this code to the chat and click \"Check\" here.",
19 | "notifications.saved": "Saved",
20 | "notifications.invalid_email": "Invalid email address",
21 | "notifications.no_bot_info": "Can not get bot info, seems token is invalid",
22 | "notifications.all_chats_deactivated": "All chats deactivated",
23 | "notifications.no_new_chats": "No new chats",
24 | "notifications.already_active": "Already active",
25 | "notifications.please_send": "Please send a code to activate",
26 | "notifications.code_not_set": "Code is not set",
27 | "notifications.code_expired": "Code has expired",
28 | "notifications.code_wrong": "Wrong code",
29 | "notifications.activated": "Activated"
30 | }
31 |
--------------------------------------------------------------------------------
/src/ext/notifications/lang/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext.notifications.name": "Уведомления",
3 | "notifications.urlconfigwarning": "Для использования Telegram требуется включить директиву 'allow_url_fopen' в настройках PHP .",
4 | "notifications.check": "Проверить",
5 | "notifications.bot_not_configured": "Бот не настроен",
6 | "notifications.g_email": "E-Mail",
7 | "notifications.h_email": "E-mail:",
8 | "notifications.d_email": "Разделите несколько адресов c помощью запятой.",
9 | "notifications.h_mailfrom": "Адрес отправителя:",
10 | "notifications.d_mailfrom": "Этот адрес будет указан как адрес отправителя в письмах с уведомлениями.",
11 | "notifications.g_telegram": "Telegram",
12 | "notifications.h_token": "Токен для бота:",
13 | "notifications.d_token": "Токен для телеграм-бота, полученный от @BotFather.",
14 | "notifications.h_active_chats": "Активные чаты:",
15 | "notifications.d_active_chats": "Количество чатов, куда бот присылает уведомления.",
16 | "notifications.deactivate_all": "Отключить все",
17 | "notifications.h_new_chat": "Новый чат:",
18 | "notifications.d_new_chat": "Начните чат с ботом, отправьте ему этот код и нажмите \"Проверить\".",
19 | "notifications.saved": "Сохранено",
20 | "notifications.invalid_email": "Некорректный адрес e-mail",
21 | "notifications.no_bot_info": "Ошибка в подключении бота; возможно, токен некорректный",
22 | "notifications.all_chats_deactivated": "All chats deactivated",
23 | "notifications.no_new_chats": "Нет новых чатов",
24 | "notifications.already_active": "Уже активирован",
25 | "notifications.please_send": "Пожалуйста, отправьте код для активации",
26 | "notifications.code_not_set": "Код не установлен",
27 | "notifications.code_expired": "Истек срок действия кода",
28 | "notifications.code_wrong": "Неправильный код",
29 | "notifications.activated": "Активирован"
30 | }
31 |
--------------------------------------------------------------------------------
/src/ext/notifications/lang/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "ext.notifications.name": "Benachrichtigungen",
3 | "notifications.urlconfigwarning": "Aktivieren Sie die PHP-Direktive 'allow_url_fopen', um Telegram-Benachrichtigungen zu verwenden.",
4 | "notifications.check": "Check",
5 | "notifications.bot_not_configured": "Bot ist nicht konfiguriert",
6 | "notifications.g_email": "E-Mail",
7 | "notifications.h_email": "E-Mail:",
8 | "notifications.d_email": "Mehrere Adressen mit Komma trennen.",
9 | "notifications.h_mailfrom": "Mail von:",
10 | "notifications.d_mailfrom": "Diese E-Mail als Absenderadresse verwenden.",
11 | "notifications.g_telegram": "Telegram",
12 | "notifications.h_token": "Bot-Token:",
13 | "notifications.d_token": "Telegram Bot API-Token von @BotFather",
14 | "notifications.h_active_chats": "Aktive Chats:",
15 | "notifications.d_active_chats": "Anzahl der Chats, bei denen der Bot Benachrichtigungen sendet.",
16 | "notifications.deactivate_all": "Alle deaktivieren",
17 | "notifications.h_new_chat": "Neuer Chat:",
18 | "notifications.d_new_chat": "Starte eine neue Unterhaltung mit dem Bot, sende diesen Code an den Chat und klicke hier auf "Überprüfen".",
19 | "notifications.saved": "Gesichert",
20 | "notifications.invalid_email": "Ungültige E-Mail Adresse",
21 | "notifications.no_bot_info": "Kann keine Bot-Informationen erhalten, Token scheint ungültig zu sein",
22 | "notifications.all_chats_deactivated": "Alle Chats deaktiviert",
23 | "notifications.no_new_chats": "Keine neuen Chats": "Keine neuen Chats",
24 | "notifications.already_active": "Bereits aktiv",
25 | "notifications.please_send": "Bitte senden Sie einen Code zur Aktivierung",
26 | "notifications.code_not_set": "Code ist nicht gesetzt",
27 | "notifications.code_expired": "Der Code ist abgelaufen",
28 | "notifications.code_wrong": "Falscher Code",
29 | "notifications.activated": "Aktiviert"
30 | }
31 |
--------------------------------------------------------------------------------
/src/includes/api/AuthController.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | class AuthController extends ApiController {
10 |
11 | function postAction($action)
12 | {
13 | switch ($action) {
14 | case 'login': $this->response->data = $this->login(); break;
15 | case 'logout': $this->response->data = $this->logout(); break;
16 | case 'session': $this->response->data = $this->createSession(); break;
17 | default: $this->response->data = ['total' => 0]; // error 400 ?
18 | }
19 | }
20 |
21 | private function login(): ?array
22 | {
23 | check_token();
24 | $t = array('logged' => 0);
25 | if (!need_auth()) {
26 | $t['disabled'] = 1;
27 | return $t;
28 | }
29 | $password = $this->req->jsonBody['password'] ?? '';
30 | if ( isPasswordEqualsToHash($password, Config::get('password')) ) {
31 | updateSessionLogged(true);
32 | $t['token'] = update_token();
33 | $t['logged'] = 1;
34 | }
35 | return $t;
36 | }
37 |
38 | private function logout(): ?array
39 | {
40 | check_token();
41 | updateSessionLogged(false);
42 | update_token();
43 | session_regenerate_id(true);
44 | $t = array('logged' => 0);
45 | return $t;
46 | }
47 |
48 | private function createSession(): ?array
49 | {
50 | $t = array();
51 | if (!need_auth()) {
52 | $t['disabled'] = 1;
53 | return $t;
54 | }
55 | if (access_token() == '') {
56 | update_token();
57 | }
58 | $t['token'] = access_token();
59 | $t['session'] = session_id();
60 | return $t;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/content/theme/style_rtl.css:
--------------------------------------------------------------------------------
1 | body { direction:rtl; }
2 | .topblock h2 {
3 | background-position: right;
4 | padding-left: 10px;
5 | padding-right: 30px;
6 | }
7 | #loading { margin-left:6px; margin-right:0px; }
8 | .bar-menu { text-align: left; }
9 | .bar-menu > * {
10 | margin-right: 10px;
11 | margin-left: unset;
12 | }
13 |
14 | .mtt-tab { margin-left:3px; margin-right:0px; border-top-right-radius:0px; border-top-left-radius:8px; }
15 | .mtt-tabs-new-button { border-top-right-radius:0px; border-top-left-radius:8px; }
16 | .mtt-tabs-select-button>span { transform:rotateY(180deg); }
17 |
18 | #task { padding-left:20px; padding-right:4px; }
19 | .mtt-taskbox-icon { left:2px; right:auto; transform:rotateY(180deg); }
20 | #newtask_adv { margin-left:0; margin-right:0.5rem; transform:rotateY(180deg); }
21 | .mtt-searchbox-icon.mtt-icon-search { right:4px; left:auto; }
22 | .mtt-searchbox-icon.mtt-icon-cancelsearch { left:4px; right:auto; }
23 |
24 | #tagcloudbtn { margin-left:0; margin-right:auto; }
25 |
26 | .task-toggle { margin-left:2px; margin-right:0; transform:rotate(180deg); }
27 | .task-middle { margin-left:0; margin-right:5px; }
28 | .task-actions { margin-left:0; margin-right:5px; }
29 |
30 | .task-prio, .task-title, .task-date, .task-tags { display:inline-block; } /* TODO: fix this */
31 |
32 | .task-prio { margin-left:5px; margin-right:0; }
33 | .task-note-block { margin-left:0; margin-right:2px; padding-left:0; padding-right:19px; background-position:right 0px;}
34 | .task-date { margin-left:0; margin-right:4px; }
35 | .duedate { margin-left:0; margin-right:5px; }
36 | .duedate-arrow { display:none; }
37 | .duedate:before { content:'\20\2190\20'; }
38 |
39 | #tagcloud { box-shadow:-1px 2px 5px rgba(0,0,0,0.5); }
40 |
41 | h3.page-title a.mtt-back-button { transform:rotate(180deg); }
42 | .form-row-short { margin-left:12px; margin-right:0; }
43 | .alltags-cell { padding-left:0; padding-right:5px; }
44 |
45 | .mtt-menu-container { box-shadow:-1px 2px 5px rgba(0,0,0,0.5); }
46 | .mtt-menu-container .menu-icon { left:auto; right:6px; }
47 | li.mtt-menu-indicator .submenu-icon { left:6px; right:auto; transform:rotate(180deg); }
48 |
--------------------------------------------------------------------------------
/src/content/theme/print.css:
--------------------------------------------------------------------------------
1 | @media print {
2 |
3 | html,body { height:auto; }
4 | h2 { display: none; }
5 | h3 { border-bottom:2px solid #777777; }
6 | #toolbar { display: none; }
7 |
8 | .topblock { display:none; }
9 | .mtt-tab { display:none; }
10 | .mtt-tab.mtt-tab-selected { display:block; border:none; background:none; margin:0; }
11 | .mtt-tab.mtt-tab-selected a { padding:0; display:inline-block; height:auto; }
12 | .mtt-tab.mtt-tab-selected .title-block { display:inline-block; }
13 | .mtt-tab.mtt-tab-selected .list-action { display:none; }
14 | .mtt-tab.mtt-tab-selected .title { text-align:left; padding:0; margin:0; max-width:none; font-size:1.3rem; color:#000; }
15 |
16 | .mtt-tabs-new-button { display:none; }
17 | #tabs_buttons { display:none; }
18 | #taskview { padding-left:0; font-weight:normal; }
19 | #taskview .arrdown { display:none; }
20 |
21 | #tasklist { list-style-type: decimal; list-style-position: outside; }
22 | #tasklist li.task-row {
23 | border-bottom:none;
24 | margin-left:2.5rem;
25 | /*border-bottom:1px solid #f0f0f0;*/
26 | border: none;
27 | }
28 | #tasklist li.task-row:hover { box-shadow: none; }
29 | #tasklist li.task-row.task-has-note.task-expanded .task-block,
30 | #tasklist li.task-row.task-has-note.clicked .task-block,
31 | #tasklist li.task-row.task-expanded { border: none; }
32 |
33 | div.task-note-block {
34 | border-left:1px solid #777777;
35 | padding-left:10px;
36 | margin-left:3px;
37 | padding-top:0px;
38 | font-size:9pt;
39 | color:#333333;
40 | }
41 | .task-middle { margin-left:0px; margin-right:3px; }
42 | .task-left { display:none; }
43 | .task-actions { display:none; }
44 | .task-date { white-space:nowrap; margin-left:10px; }
45 | #tasklist li.today, #tasklist li.past { background-color:#ffffff; border-color:#dedede; }
46 | .task-prio { font-weight:bold; }
47 |
48 | li.task-completed { opacity:1; }
49 |
50 | #footer_content { border-top:1px solid #777777; background:none; }
51 | #footer_content a { text-decoration:none; color:#000000; }
52 |
53 | #tagcloudbtn { display:none; }
54 | .mtt-notes-showhide { display:none; }
55 | #taskview img { display:none; }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/ext/backup/class.download.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | namespace BackupExtension;
10 |
11 | use BackupExtension;
12 |
13 | class Download
14 | {
15 | public $filename;
16 | public $lastErrorString = null;
17 | private $token = '';
18 |
19 | function __construct(?string $filename)
20 | {
21 | $this->filename = is_null($filename) ? MTTPATH. 'db/backup.xml' : $filename;
22 | }
23 |
24 | function checkFileAccess(?string $tokenHash = null): bool
25 | {
26 | if (!file_exists($this->filename)) {
27 | $this->lastErrorString = "Backup file not found";
28 | return false;
29 | }
30 |
31 | $this->token = access_token();
32 | if ($this->token == '') {
33 | $this->lastErrorString = "No token provided";
34 | return false;
35 | }
36 |
37 | if (!is_null($tokenHash)) {
38 | $a = explode(':', $tokenHash, 2);
39 | $rnd = $a[0] ?? '';
40 | $hash = $a[1] ?? '';
41 | if (!hash_equals(hash_hmac('sha256', $rnd, $this->token), $hash)) {
42 | $this->lastErrorString = "No temp token provided";
43 | return false;
44 | }
45 | }
46 |
47 | return true;
48 | }
49 |
50 | function downloadUrl()
51 | {
52 | $rnd = randomString();
53 | $hash = $rnd. ':'. hash_hmac('sha256', $rnd, $this->token);
54 | $url = BackupExtension::extApiActionUrl("download", "t=$hash");
55 | return $url;
56 | }
57 |
58 | function printFile()
59 | {
60 | header('Content-type: application/xml; charset=utf-8');
61 | header('Content-disposition: attachment; filename=backup.xml');
62 |
63 | $fh = fopen($this->filename, "r") or die("Couldn't open file");
64 | if ($fh) {
65 | while (!feof($fh)) {
66 | $buffer = fgets($fh, 4096);
67 | print($buffer);
68 | }
69 | fclose($fh);
70 | }
71 | exit();
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/COPYRIGHT:
--------------------------------------------------------------------------------
1 | This program is free software; you can redistribute it and/or modify
2 | it under the terms of the GNU General Public License as published by
3 | the Free Software Foundation; either version 2 of the License, or (at
4 | your option) any later version.
5 |
6 | This program is distributed in the hope that it will be useful, but
7 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8 | or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
9 | for more details.
10 |
11 | You should have received a copy of the GNU General Public License
12 | along with this program as the file LICENSE; if not, please see
13 | https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
14 |
15 |
16 | myTinyTodo
17 | --------------
18 | Url: https://www.mytinytodo.net/
19 | Copyright: 2009-2011,2019-2025 Max Pozdeev
20 | License: GPL version 2 or any later (see LICENSE file)
21 |
22 |
23 | myTinyTodo uses other works:
24 |
25 | jQuery
26 | --------------
27 | Url: http://jquery.com/
28 | Copyright: (c) JS Foundation and other contributors | https://jquery.org/license/
29 | License: MIT license. Compatible with GNU GPL (see https://blog.jquery.com/2012/09/10/jquery-licensing-changes/)
30 |
31 | jQuery UI
32 | --------------
33 | Url: http://jqueryui.com/
34 | Copyright: Copyright jQuery Foundation and other contributors
35 | License: MIT license. Compatible with GNU GPL.
36 |
37 | jQuery UI Touch Punch (fork by RWAP Software)
38 | ---------------------------------------------
39 | Url: https://github.com/RWAP/jquery-ui-touch-punch
40 | based on original touchpunch
41 | Original: https://github.com/furf/jquery-ui-touch-punch
42 | Copyright: Copyright 2011–2014, Dave Furfero
43 | License: Dual licensed under the MIT or GPL Version 2 licenses.
44 |
45 | Parsedown
46 | --------------
47 | Url: https://parsedown.org/
48 | Copyright: (c) 2013-2018 Emanuil Rusev, erusev.com
49 | License: MIT license. Compatible with GNU GPL.
50 |
51 | Other libraries (in includes/vendor)
52 | ----------------------------------------------
53 | symfony/polyfill-intl-normalizer
54 | league/commonmark
55 |
56 | Images
57 | --------------
58 | This software contains images by 3d-parties.
59 | See file content/theme/images/COPYRIGHT for details.
60 |
--------------------------------------------------------------------------------
/src/ext/notifications/class.observer.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | namespace Notify;
10 |
11 | use NotificationsExtension;
12 | use MTTNotification;
13 | use MTTNotificationCenter;
14 | use DBConnection;
15 |
16 |
17 | class NotificationObserver implements \MTTNotificationObserverInterface
18 | {
19 | private $prefs = null;
20 | private $delayedNotifications = [];
21 |
22 | public function notification(string $notification, $object)
23 | {
24 | if (!$this->prefs) {
25 | $this->init();
26 | }
27 | if (count($this->prefs['chats']) == 0 && count($this->prefs['emails']) == 0) {
28 | return; // nobody to notify
29 | }
30 |
31 | $db = DBConnection::instance();
32 | switch ($notification) {
33 | case MTTNotification::didFinishRequest:
34 | $this->processDelayed();
35 | break;
36 | case MTTNotification::didCreateTask:
37 | case MTTNotification::didCreateList:
38 | // Get list name
39 | $list = $db->sqa( "SELECT name FROM {$db->prefix}lists WHERE id=?", array($object['listId'] ?? 0) );
40 | $object['listName'] = htmlspecialchars($list['name'] ?? '');
41 | $this->delayedNotifications[] = [
42 | 'notification' => $notification,
43 | 'object' => $object
44 | ];
45 | MTTNotificationCenter::addObserverForNotification(MTTNotification::didFinishRequest, $this);
46 | }
47 | }
48 |
49 | private function processDelayed()
50 | {
51 | //$db = DBConnection::instance();
52 | $useCli = !function_exists('fastcgi_finish_request');
53 | $sender = new Sender( $this->prefs, $useCli );
54 | foreach ($this->delayedNotifications as $item) {
55 | $sender->notify($item);
56 | }
57 | }
58 |
59 | private function init()
60 | {
61 | $this->prefs = NotificationsExtension::preferences();
62 | //$this->token = $this->prefs['token'] ?? '';
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/includes/markup.commonmark.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | require_once(MTTINC. 'vendor/autoload.php');
10 |
11 | use League\CommonMark\MarkdownConverter;
12 | use League\CommonMark\Environment\Environment;
13 | use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
14 | use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
15 | use League\CommonMark\Extension\Mention\MentionExtension;
16 | use League\CommonMark\Extension\Mention\Mention;
17 |
18 | class MTTCommonmarkWrapper implements MTTMarkdownInterface
19 | {
20 | protected $toExternal;
21 |
22 | /** @var MarkdownConverter */
23 | protected $converter;
24 |
25 | function __construct()
26 | {
27 | $this->toExternal = false;
28 |
29 | $environment = new Environment([
30 | 'html_input' => 'escape',
31 | 'allow_unsafe_links' => false,
32 | 'mentions' => [
33 | 'task_id' => [
34 | 'prefix' => '#',
35 | 'pattern' => '\d+',
36 | 'generator' => function ($mention) {
37 | if (!($mention instanceof Mention)) {
38 | return null;
39 | }
40 | $mention->setUrl(\sprintf(get_mttinfo('url'). "?task=%d", $mention->getIdentifier()));
41 | if (!$this->toExternal) {
42 | $mention->data->append('attributes/class', 'mtt-link-to-task');
43 | $mention->data->append('attributes/data-target-id', $mention->getIdentifier());
44 | }
45 | return $mention;
46 | },
47 | ]
48 | ],
49 | ]);
50 | $environment->addExtension(new CommonMarkCoreExtension());
51 | $environment->addExtension(new GithubFlavoredMarkdownExtension());
52 | $environment->addExtension(new MentionExtension());
53 | $this->converter = new MarkdownConverter($environment);
54 | }
55 |
56 | public function convert(string $s, bool $toExternal = false)
57 | {
58 | $this->toExternal = $toExternal;
59 | return (string) $this->converter->convert($s);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/includes/filters.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | class MTTFilterCenter
10 | {
11 | private static $filters = [];
12 |
13 | public static function addFilterCallbackForAction(string $action, callable $callback): bool
14 | {
15 | if (!isset(self::$filters[$action])) {
16 | self::$filters[$action] = [];
17 | }
18 | if (!in_array($callback, self::$filters[$action])) {
19 | // do not duplicate same callback
20 | self::$filters[$action][] = $callback;
21 | return true;
22 | }
23 | return false;
24 | }
25 |
26 |
27 | public static function addFilterForAction(string $action, MTTFilterInterface $filter): bool
28 | {
29 | if (!isset(self::$filters[$action])) {
30 | self::$filters[$action] = [];
31 | }
32 | if (!in_array($filter, self::$filters[$action])) {
33 | // do not duplicate same filter
34 | self::$filters[$action][] = $filter;
35 | return true;
36 | }
37 | return false;
38 | }
39 |
40 |
41 | public static function hasFiltersForAction(string $action): bool
42 | {
43 | if (isset(self::$filters[$action]) && count(self::$filters[$action]) > 0) {
44 | return true;
45 | }
46 | return false;
47 | }
48 |
49 |
50 | public static function filter(string $action, $in, &$out): bool
51 | {
52 | if (!isset(self::$filters[$action]) || count(self::$filters[$action]) == 0) {
53 | return false;
54 | }
55 | foreach (self::$filters[$action] as $filter) {
56 | if ($filter instanceof MTTFilterInterface) {
57 | $filter->filter($in, $out);
58 | }
59 | else {
60 | $filter($in, $out);
61 | }
62 | }
63 | return true;
64 | }
65 | }
66 |
67 | interface MTTFilterInterface
68 | {
69 | function filter($in, &$out);
70 | }
71 |
72 | function add_filter(string $action, MTTFilterInterface $filter) {
73 | MTTFilterCenter::addFilterForAction($action, $filter);
74 | }
75 |
76 | function add_filter_callback(string $action, callable $callback) {
77 | MTTFilterCenter::addFilterCallbackForAction($action, $callback);
78 | }
79 |
80 | function do_filter(string $action, $in, &$out): bool {
81 | return MTTFilterCenter::filter($action, $in, $out);
82 | }
83 |
84 |
85 |
--------------------------------------------------------------------------------
/src/ext/updater/class.controller.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | namespace UpdaterExtension;
10 |
11 | use \UpdaterExtension;
12 | use \Config;
13 |
14 | class Controller extends \ApiController
15 | {
16 | function postCheck()
17 | {
18 | $prefs = UpdaterExtension::preferences();
19 | $updater = new Updater;
20 | $a = $updater->lastVersionInfo();
21 | if ($a) {
22 | $prefs['lastCheck'] = time();
23 | $prefs['version'] = $a['version'] ?? '';
24 | $prefs['download'] = $a['download'] ?? '';
25 | Config::saveDomain(UpdaterExtension::domain, $prefs);
26 | $this->response->data = [ 'total' => 1 ];
27 | }
28 | else {
29 | $this->response->data = [
30 | 'total' => 0,
31 | 'msg' => __("error"),
32 | 'details' => $updater->lastErrorString ?? ''
33 | ];
34 | }
35 | }
36 |
37 | function postUpdate()
38 | {
39 | $prefs = UpdaterExtension::preferences();
40 | $url = $prefs['download'] ?? '';
41 | if ($url == '') {
42 | $this->response->data = [ 'total' => 0, 'msg' => __("updater.download_error") ];
43 | return;
44 | }
45 | $updater = new Updater;
46 | $file = MTTPATH. 'update.tar.gz';
47 | if (!$updater->download($url, $file)) {
48 | $this->response->data = [
49 | 'total' => 0,
50 | 'msg' => __("updater.download_error"),
51 | 'details' => $updater->lastErrorString ?? ''
52 | ];
53 | return;
54 | }
55 | if (!$updater->extractAndReplace($file)) {
56 | $this->response->data = [
57 | 'total' => 0,
58 | 'msg' => __("updater.update_error"),
59 | 'details' => $updater->lastErrorString ?? ''
60 | ];
61 | return;
62 | }
63 | @unlink($file);
64 |
65 | if (function_exists("opcache_reset")) {
66 | opcache_reset();
67 | }
68 |
69 | // TODO: need to run post-update by new version
70 | // ...
71 | // remove /includes/lang/cns.json #renamed to zh-cn.jpon
72 |
73 | $prefs['version'] = '';
74 | $prefs['download'] = '';
75 | $prefs['lastCheck'] = 0;
76 | Config::saveDomain(UpdaterExtension::domain, $prefs);
77 |
78 | $this->response->data = [ 'total' => 1, 'msg' => __("updater.updated"), 'reload' => 'ext-settings' ];
79 | }
80 |
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/src/ext/CustomCSS/loader.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | if (!defined('MTTPATH')) {
10 | die("Unexpected usage.");
11 | }
12 |
13 | function mtt_ext_customcss_instance(): MTTExtension
14 | {
15 | return new CustomCssExtension();
16 | }
17 |
18 | class CustomCssExtension extends MTTExtension implements MTTExtensionSettingsInterface
19 | {
20 | //the same as dir name
21 | const bundleId = 'CustomCSS';
22 |
23 | // settings domain
24 | const domain = "ext.customcss.json";
25 |
26 | const cssFilename = 'custom.css';
27 |
28 | function init()
29 | {
30 | $prefs = self::preferences();
31 | if (isset($prefs['css'])) {
32 | $href = htmlspecialchars( get_unsafe_mttinfo('theme_url'). self::cssFilename. '?v='. ($prefs['edited'] ?? 0) );
33 | $cb = function() use ($href) {
34 | print "\n";
35 | };
36 | add_action('theme_head_end', $cb);
37 | }
38 | }
39 |
40 | function settingsPage(): string
41 | {
42 | $e = function($s) { return __($s, true); };
43 | $prefs = self::preferences();
44 | $css = htmlspecialchars($prefs['css'] ?? '');
45 |
46 | return
47 | <<
49 | {$e('customcss.h_css')}
50 |
{$e('customcss.d_css')}
51 |
52 |
53 |
54 | EOD;
55 | }
56 |
57 | function settingsPageType(): int
58 | {
59 | return 0; //default page
60 | }
61 |
62 | function saveSettings(array $params, ?string &$outMessage): bool
63 | {
64 | if (defined('MTT_DEMO')) {
65 | $outMessage = "Demo";
66 | return true;
67 | }
68 | $css = $params['css'] ?? '';
69 |
70 | $cssFilename = MTT_THEME_PATH. self::cssFilename;
71 | if (!file_exists($cssFilename)) {
72 | @touch($cssFilename);
73 | }
74 | if (!is_writable($cssFilename)) {
75 | $outMessage = __('customcss.not_writable');
76 | return false;
77 | }
78 | @file_put_contents($cssFilename, $css);
79 | $outMessage = __('customcss.saved');
80 | return true;
81 | }
82 |
83 | static function preferences(): array
84 | {
85 | $prefs['cssFilename'] = $cssFilename = MTT_THEME_PATH. self::cssFilename;
86 | if (file_exists($prefs['cssFilename'])) {
87 | $prefs['edited'] = filemtime($cssFilename) ?? 0;
88 | $prefs['css'] = @file_get_contents($cssFilename) ?? '';
89 | }
90 | return $prefs;
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/src/includes/markup.parsedown.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | # We do not use composer autoloader because only one class is declared in Parsedown.
10 | require_once(MTTINC. 'vendor/erusev/parsedown/Parsedown.php');
11 |
12 | class MTTParsedownWrapper implements MTTMarkdownInterface
13 | {
14 | /** @var MTTParsedown */
15 | protected $converter;
16 |
17 | function __construct()
18 | {
19 | $this->converter = new MTTParsedown();
20 | $this->converter->setSafeMode(true);
21 | //$this->converter->setBreaksEnabled(true);
22 | }
23 |
24 | public function convert(string $s, bool $toExternal = false)
25 | {
26 | $this->converter->setToExternal($toExternal);
27 | return $this->converter->text($s);
28 | }
29 | }
30 |
31 |
32 | class MTTParsedown extends Parsedown
33 | {
34 |
35 | protected $toExternal;
36 |
37 | function __construct()
38 | {
39 | $this->toExternal = false;
40 |
41 | $this->InlineTypes['#'][]= 'TaskId';
42 | $this->inlineMarkerList .= '#';
43 | }
44 |
45 | public function setToExternal(bool $v)
46 | {
47 | $this->toExternal = $v;
48 | }
49 |
50 | protected function inlineTaskId($excerpt)
51 | {
52 | if (preg_match('/^#(\d+)/', $excerpt['text'], $matches))
53 | {
54 | $attrs = array(
55 | 'href' => get_mttinfo('url'). '?task='. $matches[1],
56 | 'target' => '_blank',
57 | );
58 | if (!$this->toExternal) {
59 | $attrs['class'] = 'mtt-link-to-task';
60 | $attrs['data-target-id'] = $matches[1];
61 | }
62 | return array(
63 |
64 | // How many characters to advance the Parsedown's
65 | // cursor after being done processing this tag.
66 | 'extent' => strlen($matches[0]),
67 | 'element' => array(
68 | 'name' => 'a',
69 | 'text' => '#'. $matches[1],
70 | 'attributes' => $attrs,
71 | ),
72 |
73 | );
74 | }
75 | }
76 |
77 | protected function inlineLink($Excerpt) {
78 | $a = parent::inlineLink($Excerpt);
79 | if (is_array($a) && isset($a['element']['attributes']['href'])) {
80 | $a['element']['attributes']['target'] = '_blank';
81 | }
82 | return $a;
83 | }
84 |
85 | protected function inlineUrl($Excerpt) {
86 | $a = parent::inlineUrl($Excerpt);
87 | if (is_array($a) && isset($a['element']['attributes']['href'])) {
88 | $a['element']['attributes']['target'] = '_blank';
89 | }
90 | return $a;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/includes/api/TagsController.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | class TagsController extends ApiController {
10 |
11 | /**
12 | * Get tag cloud
13 | * @return void
14 | * @throws Exception
15 | */
16 | function getCloud($listId)
17 | {
18 | $listId = (int)$listId;
19 | checkReadAccess($listId);
20 | $db = DBConnection::instance();
21 |
22 | $sqlWhere = ($listId == -1) ? "" : "WHERE list_id = $listId";
23 | $q = $db->dq("SELECT name, tag_id, COUNT(tag_id) AS tags_count
24 | FROM {$db->prefix}tag2task INNER JOIN {$db->prefix}tags ON tag_id = id
25 | $sqlWhere
26 | GROUP BY tag_id, name
27 | ORDER BY tags_count DESC");
28 | $at = array();
29 | $ac = array();
30 | while ($r = $q->fetchAssoc()) {
31 | $at[] = array(
32 | 'name' => $r['name'],
33 | 'id' => $r['tag_id']
34 | );
35 | $ac[] = (int) $r['tags_count'];
36 | }
37 |
38 | $t = array();
39 | $t['total'] = 0;
40 | $count = count($at);
41 | if (!$count) {
42 | $this->response->data = $t;
43 | return;
44 | }
45 |
46 | $qmax = max($ac);
47 | $qmin = min($ac);
48 | if ($count >= 10) $grades = 10;
49 | else $grades = $count;
50 | $step = ($qmax - $qmin)/$grades;
51 | foreach ($at as $i => $tag)
52 | {
53 | $t['items'][] = array(
54 | 'tag' => htmlspecialchars($tag['name']),
55 | 'tagText' => (string)$tag['name'],
56 | 'id' => (int)$tag['id'],
57 | 'count' => $ac[$i],
58 | 'w' => $this->tagWeight($qmin, $ac[$i], $step)
59 | );
60 | }
61 | $t['total'] = $count;
62 | $this->response->data = $t;
63 | }
64 |
65 | /**
66 | * @return void
67 | * @throws Exception
68 | */
69 | function getSuggestions($listId)
70 | {
71 | $listId = (int)_get('list');
72 | checkWriteAccess($listId);
73 | $db = DBConnection::instance();
74 | $begin = trim(_get('q'));
75 | $limit = 8;
76 | $q = $db->dq("SELECT name, tag_id AS id FROM {$db->prefix}tags
77 | INNER JOIN {$db->prefix}tag2task ON id=tag_id
78 | WHERE list_id=$listId AND ". $db->like('name', '%s%%', $begin). "
79 | GROUP BY tag_id, name
80 | ORDER BY name
81 | LIMIT $limit");
82 | $t = array();
83 | while ($r = $q->fetchRow()) {
84 | $t[] = $r[0];
85 | }
86 | $this->response->data = $t;
87 | }
88 |
89 | private function tagWeight(int $qmin, int $q, float $step): float
90 | {
91 | if ($step == 0) return 1.0;
92 | $v = ceil(($q - $qmin)/$step);
93 | if ($v == 0) return 0.0;
94 | else return $v - 1.0;
95 | }
96 |
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/src/content/theme/dark.css:
--------------------------------------------------------------------------------
1 | /*
2 | This file is a part of myTinyTodo.
3 | */
4 |
5 |
6 | /* Dark mode */
7 |
8 | /* prefers-color-scheme media query is used in html link tag */
9 | :root {
10 |
11 | color-scheme: dark;
12 |
13 | --color-bg: #151515;
14 | --color-text-default: #eee;
15 | --color-text-reduced: #e0e0e0;
16 | --color-text-reduced2: #999;
17 | --color-text-reduced3: #666;
18 | --color-link: #6495ED; /* CornflowerBlue */
19 | --color-btn-reduced: #999;
20 | --color-btn-reduced-hover: #ddd;
21 | --color-btn-default: #fff;
22 | --color-btn-hover: #444;
23 | --color-submit: #444;
24 | --color-submit-hover: #555;
25 | --color-row-underlinig: #303030;
26 | --color-border-default: #555;
27 | --color-border-focus: #5a8df0;
28 | --color-error: #ff3333;
29 | --color-error-bg: var(--color-bg);
30 | --color-info: #EFC300;
31 | --color-info-bg: var(--color-bg);
32 | --color-input-bg: #1e1e1e;
33 |
34 | --color-toolbar: #3b3b3b;
35 | --color-btn-toolbar-hover: #555;
36 | --color-content-delimiter: #676767;
37 | --color-footer: var(--color-bg);
38 |
39 | /* Tabs */
40 | --color-tab: #1b1b1b;
41 | --color-tab-selected: var(--color-toolbar);
42 | --color-tab-hover: #262626;
43 | --color-tab-border: #676767;
44 | --color-tab-text: #ddd;
45 | --color-btn-tab: #ccc;
46 | --color-btn-tab-hover: #fff;
47 | --color-btn-tab-hover-bg: #5a5a5a;
48 |
49 | /* Menu */
50 | --color-menu: #252525;
51 | --color-menu-border: #555;
52 | --color-menu-hover: #5a8df0;
53 | --color-menu-text: #eaeaea;
54 | --color-menu-text-hover: #ebf0f8;
55 | --color-menu-text-disabled: #696969;
56 | --color-popup-shadow: 1px 2px 6px 1px rgba(85,85,85,0.1);
57 |
58 | /* Tasklist */
59 | --color-tasklist-row: var(--color-bg);
60 | --color-tasklist-row-border: var(--color-row-underlinig);
61 | --color-tasklist-row-inter-border: var(--color-tasklist-row-border);
62 | --color-tasklist-row-expanded-border: #555;
63 | --color-tasklist-tag: var(--color-tab-text);
64 | --color-tasklist-note-link: #999;
65 | --color-tasklist-link-hover: var(--color-link);
66 | --color-tasklisk-hover-shadow: rgba(255,255,255,0.4);
67 | --color-taglist-tag: var(--color-text-reduced);
68 | --color-taglist-tag-bg: #444;
69 | --color-taglist-tag-hover: var(--color-taglist-tag);
70 | --color-taglist-tag-hover-bg: var(--color-taglist-tag-bg);
71 | --color-tasklist-listname: #bbb;
72 | --color-tasklist-listname-bg: #333;
73 |
74 |
75 | /* Priority */
76 | --color-priority-none: #676767;
77 | --color-priority-text: var(--color-text-default);
78 |
79 | /* DueDates */
80 | --color-duedate-default: var(--color-tab-text);
81 | --color-duedate-soon: #008000;
82 | --color-duedate-today: #FF0000;
83 | --color-duedate-past: #B52D2D;
84 |
85 | /* Markdown */
86 | --color-md-border: #333;
87 | --color-md-bg-highlighted: #222;
88 | --color-md-text-blockquote: #777;
89 |
90 | /* Settings */
91 | --color-settings-row: #222;
92 |
93 | /* */
94 | --color-placeholder: #444;
95 | --color-placeholder-border: #555;
96 |
97 | --svg-select: var(--svg-select-dark);
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/src/content/theme/markdown.css:
--------------------------------------------------------------------------------
1 | /* Markdown notes */
2 |
3 | .markdown-note {
4 | line-height: 1.3;
5 | }
6 |
7 | .markdown-note > *:first-child { margin-top: 0 !important; }
8 | .markdown-note > *:last-child { margin-bottom: 0 !important; }
9 |
10 | .markdown-note h1 { font-size:2rem; }
11 | .markdown-note h2 { font-size:1.5rem; }
12 | .markdown-note h3 { font-size:1.2rem; }
13 | .markdown-note h4 { font-size:1rem; }
14 | .markdown-note h5 { font-size:1rem; }
15 | .markdown-note h6 { font-size:1rem; }
16 |
17 | .markdown-note hr {
18 | height: 1px;
19 | border: 0;
20 | background-color: var(--color-border-default);
21 | }
22 |
23 | .markdown-note p,
24 | .markdown-note blockquote,
25 | .markdown-note ul,
26 | .markdown-note ol,
27 | .markdown-note dl,
28 | .markdown-note table,
29 | .markdown-note pre {
30 | margin: 12px 0;
31 | }
32 | .markdown-note ul,
33 | .markdown-note ol {
34 | padding-left: 2.3rem;
35 | }
36 | .markdown-note ul.no-list,
37 | .markdown-note ol.no-list {
38 | list-style-type: none;
39 | padding: 0;
40 | }
41 |
42 | .markdown-note blockquote {
43 | margin:15px 0;
44 | border-left: 4px solid var(--color-md-border);
45 | padding: 0 15px;
46 | color: var(--color-md-text-blockquote, #777);
47 | }
48 | .markdown-note blockquote > :first-child {
49 | margin-top: 0px;
50 | }
51 | .markdown-note blockquote > :last-child {
52 | margin-bottom: 0px;
53 | }
54 |
55 | .markdown-note table {
56 | width: 100%;
57 | overflow: auto;
58 | display: block;
59 | border-spacing: 0;
60 | border-collapse: collapse;
61 | }
62 | .markdown-note table th {
63 | font-weight: bold;
64 | }
65 | .markdown-note table th,
66 | .markdown-note table td {
67 | border: 1px solid var(--color-md-border);
68 | padding: 6px 13px;
69 | }
70 | .markdown-note table tr {
71 | border-top: 1px solid var(--color-border-default);
72 | background-color: var(--color-tasklist-row);
73 | }
74 | .markdown-note table tr:nth-child(2n) {
75 | background-color: var(--color-md-bg-highlighted);
76 | }
77 |
78 |
79 | .markdown-note code, /* inline code */
80 | .markdown-note tt {
81 | font-size: 13px; /* if main font is 14px */
82 | font-family: ui-monospace,Consolas,"SF Mono",Menlo,"Noto Sans Mono","Liberation Mono",monospace;
83 | padding: 0px 5px;
84 | background-color: var(--color-md-bg-highlighted);
85 | border-radius: 3px;
86 | }
87 | .markdown-note code {
88 | white-space: break-spaces;
89 | }
90 | .markdown-note pre {
91 | font-size: 13px;
92 | font-family: ui-monospace,Consolas,"SF Mono",Menlo,"Noto Sans Mono","Liberation Mono",monospace;
93 | line-height: 1.2rem;
94 | padding: 10px;
95 | background-color: var(--color-md-bg-highlighted);
96 | overflow: auto;
97 | border-radius: 3px;
98 | }
99 | .markdown-note pre code, /* block of code */
100 | .markdown-note pre tt {
101 | margin: 0;
102 | padding: 0;
103 | background-color: transparent;
104 | border: none;
105 | }
106 | .markdown-note pre > code {
107 | white-space: pre;
108 | }
109 |
110 | .markdown-note img {
111 | max-width: 100%;
112 | }
113 |
114 |
115 | /* narrow screens */
116 | @media only screen and (max-width: 600px) {
117 |
118 | .markdown-note pre,
119 | .markdown-note code,
120 | .markdown-note tt {
121 | font-size: 14px; /* if main font is 16px */
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/includes/lang/_percent.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | $translated) {
47 | $rows[] = [$lang, "$translated/$totalKeys", round(100 * $translated/$totalKeys)."%"];
48 | }
49 |
50 | #calc column width
51 | $width = [0,0,0];
52 | foreach ($rows as $row) {
53 | $width[0] = max($width[0], strlen($row[0]));
54 | $width[1] = max($width[1], strlen($row[1]));
55 | $width[2] = max($width[2], strlen($row[2]));
56 | }
57 |
58 | # print table
59 | print "# myTinyTodo Translations\n\n";
60 | foreach ($rows as $i => $row) {
61 | if ($i == 0) {
62 | print("| ". str_pad($row[0], $width[0], " ", STR_PAD_BOTH). " | ".
63 | str_pad($row[1], $width[1], " ", STR_PAD_BOTH). " | ".
64 | str_pad($row[2], $width[2], " ", STR_PAD_BOTH). " |\n");
65 | print("|:". str_repeat("-", $width[0]). "-|-". str_repeat("-", $width[1]). ":|-". str_repeat("-", $width[2]). ":|\n");
66 | }
67 | else {
68 | print("| ". str_pad($row[0], $width[0], " ", STR_PAD_RIGHT). " | ".
69 | str_pad($row[1], $width[1], " ", STR_PAD_LEFT). " | ".
70 | str_pad($row[2], $width[2], " ", STR_PAD_LEFT). " |\n");
71 | }
72 | }
73 |
74 |
75 |
76 | function checkLang(array $src, string $file) : int
77 | {
78 | $lang = json_decode(file_get_contents($file), true) ?? [];
79 | unset($lang['_header']);
80 | $translated = checkArray($file, $src, $lang);
81 | return $translated;
82 | }
83 |
84 | function checkArray(string $file, array $src, ?array $a) : int
85 | {
86 | $translated = 0;
87 | foreach ($src as $k => $v) {
88 | if (!isset($a[$k])) {
89 | if (defined('P_VERBOSE')) {
90 | fwrite(STDERR, "$file: key `$k` is not defined\n");
91 | }
92 | continue;
93 | }
94 | if (!is_array($v)) {
95 | ++$translated;
96 | }
97 | else if (is_array($a[$k])) {
98 | ++$translated;
99 | $translated += checkArray($file, $v, $a[$k]);
100 | }
101 | else if (defined('P_VERBOSE')) {
102 | fwrite(STDERR, "$file: key `$k` is not array\n");
103 | }
104 | }
105 | return $translated;
106 | }
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/src/includes/api/ExtSettingsController.php:
--------------------------------------------------------------------------------
1 | extInstance($ext);
16 | if (!$instance) {
17 | return;
18 | }
19 | $meta = MTTExtension::extMetaInfo($ext);
20 | if (!$meta || !isset($meta['name'])) {
21 | return;
22 | }
23 |
24 | $data = $instance->settingsPage();
25 |
26 | $lang = Lang::instance();
27 | $nameKey = 'ext.'. $ext. '.name';
28 | if ($lang->hasKey($nameKey)) {
29 | $name = htmlspecialchars($lang->get($nameKey));
30 | }
31 | else {
32 | $name = htmlspecialchars($meta['name']);
33 | }
34 | $escapedExt = htmlspecialchars($ext);
35 | $e = function($s) use($lang) { return htmlspecialchars($lang->get($s)); };
36 |
37 | $formStart = '';
38 | $formEnd = '';
39 | $formButtons = '';
40 | if ($instance->settingsPageType() == 0) {
41 | $formStart = "";
43 | $formButtons =
44 | <<
46 |
47 |
48 |
49 | EOD;
50 | }
51 | $data =
52 | << $name
54 |
55 | $formStart
56 |
57 | $data
58 | $formButtons
59 |
60 | $formEnd
61 | EOD;
62 |
63 | $this->response->htmlContent($data);
64 | }
65 |
66 | /**
67 | * Save extension settings
68 | * @return void
69 | * @throws Exception
70 | */
71 | function put(string $ext)
72 | {
73 | checkWriteAccess();
74 |
75 | /** @var MTTExtension|MTTExtensionSettingsInterface $instance */
76 | $instance = $this->extInstance($ext);
77 | if (!$instance) {
78 | return;
79 | }
80 | //$userError = '';
81 | $saved = $instance->saveSettings($this->req->jsonBody ?? [], $userError);
82 | $a = [ 'saved' => (int)$saved ];
83 | if ($userError) {
84 | $a['msg'] = $userError;
85 | }
86 | $this->response->data = $a;
87 | }
88 |
89 | private function extInstance(string $ext): ?MTTExtensionSettingsInterface
90 | {
91 | $instance = MTTExtensionLoader::extensionInstance($ext);
92 | if (!$instance) {
93 | $this->response->data = [ 'msg' => "Unknown extension" ];
94 | $this->response->code = 404;
95 | return null;
96 | }
97 | if (! ($instance instanceof MTTExtensionSettingsInterface) ) {
98 | $this->response->data = [ 'msg' => "No settings page for extension" ];
99 | $this->response->code = 500;
100 | return null;
101 | }
102 | return $instance;
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/src/includes/class.dbconnection.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | class DBConnection
10 | {
11 | const DBTYPE_SQLITE = "sqlite";
12 | const DBTYPE_MYSQL = "mysql";
13 | const DBTYPE_POSTGRES = "postgres";
14 |
15 | protected static $instance;
16 |
17 | public static function init(Database_Abstract $instance) : Database_Abstract
18 | {
19 | self::$instance = $instance;
20 | return $instance;
21 | }
22 |
23 | public static function instance() : Database_Abstract
24 | {
25 | if (!isset(self::$instance)) {
26 | throw new Exception("DBConnection is not initialized");
27 | }
28 | return self::$instance;
29 | }
30 |
31 | public static function setTablePrefix($prefix)
32 | {
33 | $db = self::instance();
34 | $db->setPrefix($prefix);
35 | }
36 | }
37 |
38 | abstract class Database_Abstract
39 | {
40 | const DBTYPE = '';
41 | protected static $readonlyProps = ['prefix', 'lastQuery'];
42 |
43 | /** @var string */
44 | protected $prefix = '';
45 |
46 | /** @var string */
47 | protected $lastQuery = '';
48 |
49 | /** @var null|string */
50 | protected $logQueryToFile = null;
51 |
52 | abstract function connect(array $params): void;
53 | abstract function sq(string $query, ?array $values = null);
54 | abstract function sqa(string $query, ?array $values = null): ?array;
55 | abstract function dq(string $query, ?array $values = null): DatabaseResult_Abstract;
56 | abstract function ex(string $query, ?array $values = null): void;
57 | abstract function affected(): int;
58 | abstract function quote($value): string;
59 | abstract function quoteForLike(string $format, string $string): string;
60 | abstract function like(string $column, string $format, string $string): string;
61 | abstract function ciEquals(string $column, string $value): string;
62 | abstract function lastInsertId(?string $name = null): ?string;
63 | abstract function tableExists(string $table): bool;
64 | abstract function tableFieldExists(string $table, string $field): bool;
65 |
66 | function __get(string $propName) {
67 | if ( in_array($propName, self::$readonlyProps) ) {
68 | return $this->{$propName};
69 | }
70 | throw new Error("Attempt to read undefined property ". get_class($this). "::\$$propName");
71 | }
72 |
73 | function setPrefix(string $prefix): void {
74 | if ($prefix != '' && !preg_match("/^[a-zA-Z0-9_]+$/", $prefix)) {
75 | throw new Exception("Incorrect table prefix");
76 | }
77 | $this->prefix = $prefix;
78 | }
79 |
80 | function setLogQueryToFile(?string $path) {
81 | //any checks?
82 | $this->logQueryToFile = $path;
83 | }
84 |
85 | function setLastQuery(string $lastQuery) {
86 | $this->lastQuery = $lastQuery;
87 | if (MTT_DEBUG && $this->logQueryToFile !== null) {
88 | $f = fopen($this->logQueryToFile, "a");
89 | if ($f) {
90 | fwrite($f, $this->lastQuery . "\n");
91 | fclose($f);
92 | }
93 | }
94 | }
95 | }
96 |
97 | abstract class DatabaseResult_Abstract
98 | {
99 | abstract function fetchRow(): ?array;
100 | abstract function fetchAssoc(): ?array;
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/src/index.php:
--------------------------------------------------------------------------------
1 |
5 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
6 | */
7 |
8 | $checkDbExists = true;
9 | require_once('./init.php');
10 |
11 | //Parse query string
12 | if ( isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] != '' ) {
13 | parseRoute($_SERVER['QUERY_STRING']);
14 | }
15 |
16 |
17 | $lang = Lang::instance();
18 |
19 | if ($lang->rtl()) {
20 | Config::set('rtl', 1);
21 | }
22 |
23 | if (!is_int(Config::get('firstdayofweek')) || Config::get('firstdayofweek')<0 || Config::get('firstdayofweek')>6) {
24 | Config::set('firstdayofweek', 1);
25 | }
26 |
27 | if ( access_token() == '' ) {
28 | update_token();
29 | }
30 |
31 | if (MTT_THEME != 'theme') {
32 | // custom theme
33 | require(MTT_THEME_PATH. 'index.php');
34 | }
35 | else {
36 | require(MTTINC. 'theme.php');
37 | }
38 |
39 | MTTNotificationCenter::postDidFinishRequestNotification();
40 | // end
41 |
42 |
43 | function parseRoute($queryString)
44 | {
45 | parse_str($queryString, $q);
46 | if (isset($q['list'])) {
47 | $hash = ($q['list'] == 'alltasks') ? ['alltasks'] : ['list', (int)$q['list']];
48 | unset($q['list']);
49 | redirectWithHashRoute($hash, $q);
50 | }
51 | else if (isset($q['task'])) {
52 | $listId = (int)DBCore::default()->getListIdByTaskId((int)$q['task']);
53 | if ($listId > 0) {
54 | $h = [ 'list', $listId, 'search', '#'. (int)$q['task']];
55 | redirectWithHashRoute($h);
56 | }
57 | // TODO: not found
58 | }
59 | }
60 |
61 | function redirectWithHashRoute(array $hash, array $q = [])
62 | {
63 | $url = get_unsafe_mttinfo('url');
64 | $query = http_build_query($q);
65 | if ($query != '') $url .= "?$query";
66 | if (count($hash) > 0) {
67 | $encodedHash = implode("/", array_map("rawurlencode", $hash));
68 | $url .= "#$encodedHash";
69 | }
70 | header("Location: ". $url);
71 | exit;
72 | }
73 |
74 | function js_options()
75 | {
76 | // Here we can use URIs instead of full URLs.
77 | $homeUrl = htmlspecialchars(Config::getUrl('url'));
78 | if ($homeUrl == '') {
79 | $homeUrl = get_mttinfo('mtt_uri');
80 | }
81 | $a = array(
82 | "token" => htmlspecialchars(access_token()),
83 | "title" => get_unsafe_mttinfo('title'),
84 | "lang" => Lang::instance()->jsStrings(),
85 | "mttUrl" => get_mttinfo('mtt_uri'),
86 | "homeUrl" => $homeUrl,
87 | "apiUrl" => get_mttinfo('api_url'),
88 | "needAuth" => need_auth() ? true : false,
89 | "isLogged" => is_logged() ? true : false,
90 | "showdate" => Config::get('showdate') ? true : false,
91 | "showtime" => Config::get('showtime') ? true : false,
92 | "showdateInline" => Config::get('showdateInline') ? true : false,
93 | "duedatepickerformat" => htmlspecialchars(Config::get('dateformat2')),
94 | "firstdayofweek" => (int) Config::get('firstdayofweek'),
95 | "calendarIcon" => get_mttinfo('theme_url'). 'images/calendar.svg',
96 | "autotag" => Config::get('autotag') ? true : false,
97 | "markdown" => Config::get('markup') == 'v1' ? false : true,
98 | "newTaskCounter" => Config::get('newTaskCounter') ? true : false,
99 | "newTaskCounterIcon" => Config::get('newTaskCounterIcon') ? true : false,
100 | );
101 | $flags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE;
102 | if (MTT_DEBUG) {
103 | $flags |= JSON_PRETTY_PRINT;
104 | }
105 | $json = json_encode($a, $flags);
106 | if ($json === false) {
107 | error_log("MTT Error: Failed to encode array of options to JSON. Code: ". (int)json_last_error());
108 | echo "{}";
109 | }
110 | else {
111 | echo $json;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/includes/markup.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | require_once(MTTINC. 'markup.parsedown.php');
10 | //require_once(MTTINC. 'markup.commonmark.php');
11 |
12 | interface MTTMarkdownInterface
13 | {
14 | public function convert(string $s, bool $toExternal = false);
15 | }
16 |
17 | final class MTTMarkdown
18 | {
19 | /** @var MTTMarkdownInterface */
20 | private static $instance;
21 |
22 | /** @var string */
23 | private static $instanceClass = MTTParsedownWrapper::class;
24 | //private static $instanceClass = MTTCommonmarkWrapper::class;
25 |
26 | /**
27 | *
28 | * @return MTTMarkdownInterface
29 | */
30 | public static function instance() : MTTMarkdownInterface
31 | {
32 | if (isset(self::$instance))
33 | return self::$instance;
34 |
35 | // if (do_filter('markdownConverterClass', self::$instanceClass, $newClass) && $newClass) {
36 | // self::setInstanceClass($newClass);
37 | // }
38 |
39 | self::$instance = new self::$instanceClass();
40 | return self::$instance;
41 | }
42 |
43 | public static function setInstanceClass(string $class)
44 | {
45 | if (!is_a($class, MTTMarkdownInterface::class, true)) {
46 | throw new Exception("Class '$class' is not a MTTMarkdownInterface");
47 | }
48 | self::$instanceClass = $class;
49 | self::$instance = null;
50 | }
51 | }
52 |
53 | function noteMarkup($note, $toExternal = false)
54 | {
55 | if ($note === null) {
56 | $note = '';
57 | }
58 | if (Config::get('markup') == 'v1') {
59 | return mttMarkup_v1($note);
60 | }
61 | return markdownToHtml($note, $toExternal);
62 | }
63 |
64 | function markdownToHtml($s, $toExternal = false)
65 | {
66 | return MTTMarkdown::instance()->convert($s, $toExternal);
67 | }
68 |
69 |
70 | // Convert note's raw text to html with allowed elements (b,i,u,s and raw urls)
71 | function mttMarkup_v1($s)
72 | {
73 | //hide allowed elements from escaping
74 | $c1 = chr(1);
75 | $c2 = chr(2);
76 | $s = preg_replace("~([\s\S]*?)~i", "{$c1}b{$c2}\$1{$c1}/b{$c2}", $s);
77 | $s = preg_replace("~([\s\S]*?)~i", "{$c1}i{$c2}\$1{$c1}/i{$c2}", $s);
78 | $s = preg_replace("~([\s\S]*?)~i", "{$c1}u{$c2}\$1{$c1}/u{$c2}", $s);
79 | $s = preg_replace("~([\s\S]*?)~i", "{$c1}s{$c2}\$1{$c1}/s{$c2}", $s);
80 | $s = htmlspecialchars($s, ENT_QUOTES); //escape all elements, except above
81 | $s = str_replace( [$c1, $c2], ['<','>'], $s ); //unhide
82 | $s = nl2br($s);
83 |
84 | // make links from text starting with 'www.'
85 | $s = preg_replace(
86 | "/(^|\s|>)(www\.([\w\#$%&~\/.\-\+;:=,\?\[\]@]+?))(,|\.|:|)?(?=\s|"|<|>|\"|<|>|$)/iu" ,
87 | '$1$2$4' ,
88 | $s
89 | );
90 |
91 | // make link from text starting with protocol like 'http://'
92 | $s = preg_replace(
93 | "/(^|\s|>)([a-z]+:\/\/([\w\#$%&~\/.\-\+;:=,\?\[\]@]+?))(,|\.|:|)?(?=\s|"|<|>|\"|<|>|$)/iu" ,
94 | '$1$2$4' ,
95 | $s
96 | );
97 |
98 | return $s;
99 | }
100 |
101 | // Convert raw title to html with allowed urls
102 | function titleMarkup($title)
103 | {
104 | //escape all unsafe
105 | $title = htmlspecialchars($title, ENT_QUOTES);
106 |
107 | // make links from text starting with 'www.'
108 | $title = preg_replace(
109 | "/(^|\s|>)(www\.([\w\#$%&~\/.\-\+;:=,\?\[\]@]+?))(,|\.|:|)?(?=\s|"|<|>|\"|<|>|$)/iu" ,
110 | '$1$2$4' ,
111 | $title
112 | );
113 |
114 | // make link from text starting with protocol like 'http://'
115 | $title = preg_replace(
116 | "/(^|\s|>)([a-z]+:\/\/([\w\#$%&~\/.\-\+;:=,\?\[\]@]+?))(,|\.|:|)?(?=\s|"|<|>|\"|<|>|$)/iu" ,
117 | '$1$2$4' ,
118 | $title
119 | );
120 | return $title;
121 | }
122 |
123 |
--------------------------------------------------------------------------------
/src/ext/notifications/class.controller.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | namespace Notify;
10 |
11 | use NotificationsExtension;
12 | use Config;
13 |
14 | class Controller extends \ApiController
15 | {
16 | function postDeactivateAll()
17 | {
18 | $prefs = Config::requestDomain(NotificationsExtension::domain);
19 | if (isset($prefs['chats'])) {
20 | $prefs['chats'] = [];
21 | Config::saveDomain(NotificationsExtension::domain, $prefs);
22 | }
23 | $this->response->data = [ 'total' => 1, 'msg' => __("notifications.all_chats_deactivated") ];
24 | }
25 |
26 | function postCheck()
27 | {
28 | $prefs = Config::requestDomain(NotificationsExtension::domain);
29 | if (!($prefs['validToken'] ?? false)) {
30 | $this->response->data = [ 'total' => 0, 'msg' => __("notifications.bot_not_configured") ];
31 | return;
32 | }
33 | if (!isset($prefs['chats']) || !is_array($prefs['chats'])) {
34 | $prefs['chats'] = [];
35 | }
36 | $code = $prefs['code'] ?? null;
37 | $codeExpires = $prefs['codeExpires'] ?? 0;
38 | $token = $prefs['token'] ?? '';
39 |
40 | $this->response->data = [ 'total' => 0, 'msg' => __("notifications.no_new_chats") ];
41 |
42 | // Read messages since last check
43 | $maxId = $prefs['lastUpdateId'] ?? 0;
44 | $api = new TelegramApi($token);
45 | $api->logApiErrors = true;
46 | $updates = $api->getUpdates([
47 | 'offset' => $maxId + 1,
48 | 'allowed_updates' => ['message']
49 | ]);
50 | if (!is_array($updates) || count($updates) == 0) {
51 | return;
52 | }
53 |
54 | // Select last message in every chat
55 | $messages = array();
56 | foreach ($updates as $update) {
57 | $message = $update['message'] ?? [];
58 | $chatId = (string)($message['chat']['id'] ?? 0);
59 | $prefs['lastUpdateId'] = max($maxId, $update['update_id'] ?? 0);
60 | $messages[$chatId] = $message;
61 | }
62 |
63 | $total = 0;
64 | foreach ($messages as $chatId => $message) {
65 | $chatId = (int) $chatId;
66 | $text = $message['text'] ?? '';
67 | $msgId = (int) ($message['message_id'] ?? 0);
68 | if (in_array($chatId, $prefs['chats'])) {
69 | $api->sendMessage([
70 | 'chat_id' => $chatId,
71 | 'text' => __("notifications.already_active")
72 | ]);
73 | }
74 | else if ($text === '/start') {
75 | $api->sendMessage([
76 | 'chat_id' => $chatId,
77 | 'text' => __("notifications.please_send")
78 | ]);
79 | }
80 | else if ($code === null) {
81 | $api->sendMessage([
82 | 'chat_id' => $chatId,
83 | 'text' => __("notifications.code_not_set")
84 | ]);
85 | }
86 | else if ($codeExpires < time()) {
87 | $api->sendMessage([
88 | 'chat_id' => $chatId,
89 | 'text' => __("notifications.code_expired")
90 | ]);
91 | }
92 | else if ($text == $code) {
93 | $prefs['chats'][] = $chatId;
94 | $api->sendMessage([
95 | 'chat_id' => $chatId,
96 | 'reply_to_message_id' => $msgId,
97 | 'text' => __("notifications.activated")
98 | ]);
99 | $total++;
100 | $this->response->data = [
101 | 'total' => $total,
102 | 'msg' => __("notifications.activated")
103 | ];
104 | }
105 | else {
106 | $api->sendMessage([
107 | 'chat_id' => $chatId,
108 | 'reply_to_message_id' => $msgId,
109 | 'text' => __("notifications.code_wrong")
110 | ]);
111 | }
112 | }
113 |
114 | Config::saveDomain(NotificationsExtension::domain, $prefs);
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/src/includes/notifications.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | class MTTNotificationCenter
10 | {
11 | /**
12 | * @var array
13 | */
14 | private static $observers = [];
15 |
16 | /**
17 | * @param string $notification
18 | * @param MTTNotificationObserverInterface $observer
19 | * @return void
20 | */
21 | public static function addObserverForNotification(string $notification, MTTNotificationObserverInterface $observer)
22 | {
23 | if (!isset(self::$observers[$notification])) {
24 | self::$observers[$notification] = [];
25 | }
26 | if (!in_array($observer, self::$observers[$notification])) {
27 | // do not duplicate same observer
28 | self::$observers[$notification][] = $observer;
29 | }
30 | }
31 |
32 | /**
33 | * @param string[] $notifications
34 | * @param MTTNotificationObserverInterface $observer
35 | * @return void
36 | */
37 | public static function addObserverForNotifications(array $notifications, MTTNotificationObserverInterface $observer)
38 | {
39 | foreach ($notifications as $notification) {
40 | self::addObserverForNotification($notification, $observer);
41 | }
42 | }
43 |
44 | /**
45 | *
46 | * @param string $notification
47 | * @param callable $callback
48 | * @return void
49 | */
50 | public static function addCallbackForNotification(string $notification, callable $callback)
51 | {
52 | if (!isset(self::$observers[$notification])) {
53 | self::$observers[$notification] = [];
54 | }
55 | self::$observers[$notification][] = $callback;
56 | }
57 |
58 | /**
59 | *
60 | * @param string $notification
61 | * @return bool
62 | */
63 | public static function hasObserversForNotification(string $notification): bool
64 | {
65 | if (isset(self::$observers[$notification]) && count(self::$observers[$notification]) > 0) {
66 | return true;
67 | }
68 | return false;
69 | }
70 |
71 | public static function postNotification(string $notification, $object)
72 | {
73 | if (!isset(self::$observers[$notification])) {
74 | return; // No observers for this notification
75 | }
76 | foreach (self::$observers[$notification] as $observer) {
77 | if ($observer instanceof MTTNotificationObserverInterface) {
78 | $observer->notification($notification, $object);
79 | }
80 | else {
81 | $observer($object);
82 | }
83 | }
84 | }
85 |
86 | /**
87 | * Run this near exit()
88 | * @return void
89 | */
90 | public static function postDidFinishRequestNotification()
91 | {
92 | if ( ! isset(self::$observers[MTTNotification::didFinishRequest]) ) {
93 | return; // No observers for didFinishRequest
94 | }
95 | if (function_exists('fastcgi_finish_request')) {
96 | if (session_status() == PHP_SESSION_ACTIVE) {
97 | session_write_close(); // Close active session
98 | }
99 | fastcgi_finish_request();
100 | }
101 | self::postNotification(MTTNotification::didFinishRequest, null);
102 | }
103 | }
104 |
105 | interface MTTNotificationObserverInterface
106 | {
107 | function notification(string $notification, $object);
108 | }
109 |
110 | // Enum
111 | abstract class MTTNotification
112 | {
113 | const didFinishRequest = 'didFinishRequest';
114 | const didCreateTask = 'didCreateTask';
115 | const didEditTask = 'didEditTask';
116 | const didDeleteTask = 'didDeleteTask';
117 | const didCompleteTask = 'didCompleteTask';
118 | const didCreateList = 'didCreateList';
119 | const didDeleteList = 'didDeleteList';
120 | const didDeleteCompletedInList = 'didDeleteCompletedInList';
121 | }
122 |
123 | function add_action(string $notification, callable $callback)
124 | {
125 | MTTNotificationCenter::addCallbackForNotification($notification, $callback);
126 | }
127 |
128 | function do_action(string $notification, $object = null)
129 | {
130 | MTTNotificationCenter::postNotification($notification, $object);
131 | }
132 |
--------------------------------------------------------------------------------
/src/ext/updater/loader.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | if (!defined('MTTPATH')) {
10 | die("Unexpected usage.");
11 | }
12 |
13 | require_once('class.controller.php');
14 | require_once('class.updater.php');
15 |
16 | function mtt_ext_updater_instance(): MTTExtension
17 | {
18 | return new UpdaterExtension();
19 | }
20 |
21 | use UpdaterExtension\Controller;
22 | use UpdaterExtension\Updater;
23 |
24 | class UpdaterExtension extends MTTExtension implements MTTExtensionSettingsInterface, MTTHttpApiExtender
25 | {
26 | //the same as dir name
27 | const bundleId = 'updater';
28 |
29 | // settings domain
30 | const domain = "ext.updater.json";
31 |
32 | function init()
33 | {
34 | }
35 |
36 | // MTTHttpApiExtender
37 | function extendHttpApi(): array
38 | {
39 | return array(
40 | '/check' => [
41 | 'POST' => [ Controller::class , 'postCheck' ],
42 | ],
43 | '/update' => [
44 | 'POST' => [ Controller::class , 'postUpdate' ],
45 | ]
46 | );
47 | }
48 |
49 | function settingsPage(): string
50 | {
51 | $e = function($s, $arg=null) { return __($s, true, $arg); };
52 | $ext = htmlspecialchars(self::bundleId);
53 | $prefs = self::preferences();
54 | $lastCheck = $prefs['lastCheck'] ?? 0;
55 | $version = $prefs['version'] ?? '';
56 | $updateStr = '';
57 | $curVersion = htmlspecialchars(mytinytodo\Version::VERSION);
58 | $err = null;
59 | if (time() - $lastCheck > 86400*7) {
60 | $updater = new Updater;
61 | $a = $updater->lastVersionInfo();
62 | if ($a) {
63 | $lastCheck = $prefs['lastCheck'] = time();
64 | $version = $prefs['version'] = $a['version'] ?? '';
65 | $prefs['download'] = $a['download'] ?? '';
66 | Config::saveDomain(self::domain, $prefs);
67 | }
68 | else {
69 | $err = $updater->lastErrorString;
70 | }
71 | }
72 | $warning = '';
73 | if ($version != '') {
74 | if ( version_compare($version, mytinytodo\Version::VERSION) > 0 ) {
75 | $updateStr = "
{$e('updater.new_version_available')}: ". htmlspecialchars($version);
76 | # allow update to v1.7.x and 1.8.x only
77 | if ( in_array(substr($version, 0, 4), ["1.7.", "1.8."]) ) {
78 | $updateStr .= "
\n {$e('updater.update')} ";
79 | }
80 | $retval = 0;
81 | $output = null;
82 | unset($output);
83 | @exec('tar --version', $output, $retval);
84 | if ($retval != 0) {
85 | $warning = "⚠️ {$e('updater.tarwarning')}
";
86 | }
87 | }
88 | else {
89 | $updateStr = "
{$e('updater.no_updates')}
{$e('updater.last_version', $version)}";
90 | }
91 | }
92 | $lastCheckStr = $err ? $e('updater.download_error') : ($lastCheck ? timestampToDatetime($lastCheck, true) : "");
93 |
94 | if (!boolval(ini_get('allow_url_fopen'))) {
95 | $warning .= "⚠️ {$e('updater.urlconfigwarning')}
";
96 | }
97 |
98 |
99 | return
100 | <<
103 | {$e('updater.h_check_updates')}
104 |
105 | {$e('updater.current_version')}: $curVersion
106 | {$e('updater.last_checked')}: $lastCheckStr
107 | $updateStr
108 |
109 |
110 | EOD;
111 | }
112 |
113 | function settingsPageType(): int
114 | {
115 | return 1; // no form buttons
116 | }
117 |
118 | function saveSettings(array $params, ?string &$outMessage): bool
119 | {
120 | return true;
121 | }
122 |
123 |
124 | static function preferences(): array
125 | {
126 | $prefs = Config::requestDomain(self::domain);
127 | return $prefs;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/includes/class.sessionhandler.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | class MTTSessionHandler implements SessionHandlerInterface, SessionUpdateTimestampHandlerInterface
10 | {
11 | /**
12 | * @var Database_Abstract
13 | */
14 | private $db;
15 |
16 | private $isEmptyData = false;
17 |
18 | /**
19 | * @param string $path
20 | * @param string $name
21 | * @return bool
22 | * @throws Exception
23 | */
24 | public function open($path, $name): bool
25 | {
26 | $this->db = DBConnection::instance();
27 | return true;
28 | }
29 |
30 | /**
31 | * @return bool
32 | */
33 | public function close(): bool
34 | {
35 | return true;
36 | }
37 |
38 | /**
39 | * @param string $id
40 | * @return string
41 | * @throws Exception
42 | */
43 | #[\ReturnTypeWillChange]
44 | public function read($id)
45 | {
46 | // read session data if not expired
47 | $time = time();
48 | $r = $this->db->sq("SELECT data,last_access,expires FROM {$this->db->prefix}sessions WHERE id = ?", [$id]);
49 | if ( is_null($r) ) {
50 | // We return '' instead of false to avoid warning
51 | return '';
52 | }
53 | if ( (int)$r[2] < $time) {
54 | // maybe regenerate id?
55 | $r[0] = '';
56 | }
57 |
58 | // update last access time and set expires in 14 days
59 | // refresh every 8 hours
60 | if ( $r[1] + 28800 < $time ) {
61 | $expire = $time + 14 * 86400;
62 | $this->db->ex("UPDATE {$this->db->prefix}sessions SET last_access=?,expires=? WHERE id = ?",
63 | array($time, $expire, $id) );
64 | }
65 |
66 | if ($r[0] === '') {
67 | $this->isEmptyData = true;
68 | }
69 | return $r[0];
70 | }
71 |
72 | /**
73 | * @param string $id
74 | * @param string $data
75 | * @return bool
76 | * @throws Exception
77 | */
78 | public function write($id, $data): bool
79 | {
80 | // Ignore empty sessions without changes
81 | if ($this->isEmptyData && $data === '')
82 | return true;
83 |
84 | $time = time();
85 | $expire = $time + 14 * 86400;
86 |
87 | $exists = $this->db->sq("SELECT COUNT(*) FROM {$this->db->prefix}sessions WHERE id = ?", [$id]);
88 | if (!$exists) {
89 | // Create new session with 14 days lifetime
90 | $this->db->ex("INSERT INTO {$this->db->prefix}sessions (id,data,last_access,expires) VALUES (?,?,?,?)",
91 | array($id, $data, $time, $expire) );
92 | }
93 | else {
94 | // Update existing session
95 | $this->db->ex("UPDATE {$this->db->prefix}sessions SET data = ?, last_access=?, expires=? WHERE id = ?",
96 | array($data, $time, $expire, $id) );
97 | }
98 | return true;
99 | }
100 |
101 | /**
102 | * @param string $id
103 | * @return bool
104 | * @throws Exception
105 | */
106 | public function destroy($id): bool
107 | {
108 | $this->db->ex("DELETE FROM {$this->db->prefix}sessions WHERE id = ?", [$id]);
109 | return true;
110 | }
111 |
112 | /**
113 | * @param int $max_lifetime
114 | * @return int|false
115 | */
116 | #[\ReturnTypeWillChange]
117 | public function gc($max_lifetime)
118 | {
119 | // We ignore php runtime 'session.gc_maxlifetime'
120 | $expire = time();
121 | $this->db->ex("DELETE FROM {$this->db->prefix}sessions WHERE expires < $expire");
122 | return $this->db->affected();
123 | }
124 |
125 |
126 | /**
127 | * SessionUpdateTimestampHandlerInterface::validateId
128 | * @param string $id
129 | * @return bool
130 | */
131 | public function validateId($id): bool
132 | {
133 | $r = $this->db->sq("SELECT COUNT(*) FROM {$this->db->prefix}sessions WHERE id = ?", [$id]);
134 | if ($r)
135 | return true;
136 | return false;
137 | }
138 |
139 | /**
140 | * SessionUpdateTimestampHandlerInterface::updateTimestamp
141 | * @param string $id
142 | * @param string $data
143 | * @return bool
144 | */
145 | public function updateTimestamp($id, $data): bool
146 | {
147 | // Warning if return false
148 | return true;
149 | }
150 | }
151 |
152 |
--------------------------------------------------------------------------------
/src/ext/backup/loader.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | if (!defined('MTTPATH')) {
10 | die("Unexpected usage.");
11 | }
12 |
13 | require_once('class.controller.php');
14 |
15 | function mtt_ext_backup_instance(): MTTExtension
16 | {
17 | return new BackupExtension();
18 | }
19 |
20 | use BackupExtension\Controller;
21 |
22 | class BackupExtension extends MTTExtension implements MTTExtensionSettingsInterface, MTTHttpApiExtender
23 | {
24 | //the same as dir name
25 | const bundleId = 'backup';
26 |
27 | // settings domain
28 | const domain = "ext.backup.json";
29 |
30 | function init() {
31 | }
32 |
33 | // MTTHttpApiExtender
34 | function extendHttpApi(): array
35 | {
36 | return array(
37 | '/makeBackup' => [
38 | 'POST' => [ Controller::class , 'postMakeBackup' ],
39 | ],
40 | '/download' => [
41 | 'POST' => [ Controller::class , 'postDownload' ],
42 | 'GET' => [ Controller::class , 'getDownload', true ], // doesn't check auth token
43 | ],
44 | '/restore' => [
45 | 'POST' => [ Controller::class , 'postRestore' ],
46 | ],
47 | '/checkInconsistency' => [
48 | 'POST' => [ Controller::class , 'postCheckInconsistency' ],
49 | ],
50 | '/repairInconsistency' => [
51 | 'POST' => [ Controller::class , 'postRepairInconsistency' ],
52 | ],
53 |
54 | );
55 | }
56 |
57 | function settingsPage(): string
58 | {
59 | $warning = '';
60 | $e = function($s, $arg=null) { return __($s, true, $arg); };
61 | $ext = htmlspecialchars(self::bundleId);
62 |
63 | $downloadDisabled = '';
64 | $lastBackup = '';
65 | $filename = MTTPATH. 'db/backup.xml';
66 | if (file_exists($filename)) {
67 | $time = filemtime($filename);
68 | $lastBackup = htmlspecialchars( sprintf($e('backup.last_backup'), formatTime(Config::get('dateformat'). " H:i:s", $time)) );
69 | }
70 | else {
71 | $downloadDisabled = 'disabled';
72 | }
73 |
74 | return <<
77 | function onBackupFileChange(el) {
78 | const fd = new FormData();
79 | fd.append('file', el.files[0]);
80 | mytinytodo.extensionSettingsAction(el.dataset.extSettingsAction, el.dataset.ext, fd);
81 | }
82 |
83 |
84 |
{$e('backup.h_make')}
85 |
{$e('backup.d_make', 'db')}
86 |
87 |
88 |
89 |
$lastBackup
90 |
91 |
92 |
93 |
94 |
{$e('backup.h_inconsistency')}
95 |
{$e('backup.d_inconsistency')}
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
{$e('backup.h_restore')}
104 |
{$e('backup.d_restore')}
105 |
106 |
107 |
111 |
112 |
113 | EOD;
114 | }
115 |
116 | function settingsPageType(): int
117 | {
118 | return 1; //no form buttons
119 | }
120 |
121 | function saveSettings(array $params, ?string &$outMessage): bool
122 | {
123 | return false;
124 | }
125 | /*
126 | static function preferences(): array
127 | {
128 | return [
129 | 'backupFilePath' => MTTPATH. 'db/backup.xml'
130 | ];
131 | }
132 | */
133 | static function backupFilePath()
134 | {
135 | //return self::preferences()['backupFilePath'];
136 | return MTTPATH. 'db/backup.xml';
137 | }
138 |
139 |
140 | }
141 |
--------------------------------------------------------------------------------
/src/ext/backup/class.controller.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | namespace BackupExtension;
10 |
11 | use BackupExtension;
12 | use BackupExtension\Backup;
13 | use BackupExtension\Download;
14 | use BackupExtension\Check;
15 |
16 | class Controller extends \ApiController
17 | {
18 | function postMakeBackup()
19 | {
20 | require_once('class.backup.php');
21 | $filename = BackupExtension::backupFilePath();
22 | $backup = new Backup($filename);
23 |
24 | if (!$backup->makeBackup()) {
25 | $this->response->data = [
26 | 'total' => 0,
27 | 'msg' => __("error"),
28 | 'details' => $backup->lastErrorString ?? '',
29 | ];
30 | }
31 |
32 | $this->response->data = [
33 | 'total' => 1,
34 | 'ok' => true,
35 | 'msg' => __("backup.done"),
36 | 'alertTextOnLoad' => __("backup.done"),
37 | ];
38 | }
39 |
40 | function postDownload()
41 | {
42 | require_once('class.download.php');
43 | $filename = BackupExtension::backupFilePath();
44 | $download = new Download($filename);
45 |
46 | if (!$download->checkFileAccess()) {
47 | $this->response->data = [
48 | 'total' => 0,
49 | 'msg' => __("error"),
50 | 'details' => $download->lastErrorString ?? '',
51 | ];
52 | return;
53 | }
54 | $this->response->data = [
55 | 'total' => 1,
56 | 'redirect' => $download->downloadUrl()
57 | ];
58 | }
59 |
60 | function getDownload()
61 | {
62 | require_once('class.download.php');
63 | $filename = BackupExtension::backupFilePath();
64 | $download = new Download($filename);
65 |
66 | $ott = (string)_get('t');
67 | if (!$download->checkFileAccess($ott)) {
68 | $this->response->data = [
69 | 'total' => 0,
70 | 'msg' => __("error"),
71 | 'details' => $download->lastErrorString ?? '',
72 | ];
73 | return;
74 | }
75 | $download->printFile();
76 | exit();
77 | }
78 |
79 | function postRestore()
80 | {
81 | require_once('class.restore.php');
82 | $restore = new Restore();
83 |
84 | if (!$restore->isUploaded()) {
85 | $this->response->data = [
86 | 'total' => 0,
87 | 'msg' => __("error"),
88 | 'details' => $restore->lastErrorString ?? '',
89 | ];
90 | return;
91 | }
92 |
93 | if (!$restore->restore()) {
94 | $this->response->data = [
95 | 'total' => 0,
96 | 'msg' => __("error"),
97 | 'details' => $restore->lastErrorString ?? '',
98 | ];
99 | return;
100 | }
101 |
102 | $this->response->data = [
103 | 'total' => 1,
104 | 'msg' => __("backup.done"),
105 | 'redirect' => get_mttinfo('url'),
106 | ];
107 | }
108 |
109 | function postCheckInconsistency()
110 | {
111 | require_once('class.check.php');
112 | $check = new Check();
113 |
114 | if (!$check->check()) {
115 | $this->response->data = [
116 | 'total' => 0,
117 | 'msg' => __("error"),
118 | 'details' => $check->lastErrorString ?? '',
119 | ];
120 | return;
121 | }
122 | $this->response->data = [
123 | 'total' => 1,
124 | 'ok' => true,
125 | 'msg' => __("backup.done"),
126 | ];
127 | if ($check->report == 'OK') {
128 | $this->response->data['alertText'] = "OK";
129 | }
130 | else {
131 | $this->response->data['html'] = "". htmlspecialchars($check->report). "
";
132 | }
133 | }
134 |
135 | function postRepairInconsistency()
136 | {
137 | require_once('class.check.php');
138 | $check = new Check();
139 |
140 | if (!$check->repair()) {
141 | $this->response->data = [
142 | 'total' => 0,
143 | 'msg' => __("error"),
144 | 'details' => $check->lastErrorString ?? '',
145 | ];
146 | return;
147 | }
148 | $this->response->data = [
149 | 'total' => 1,
150 | 'ok' => true,
151 | 'msg' => __("backup.done"),
152 | 'alertText' => __("backup.done"),
153 | ];
154 | }
155 |
156 | }
157 |
--------------------------------------------------------------------------------
/src/ext/backup/class.check.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | namespace BackupExtension;
10 |
11 | use DBConnection;
12 |
13 | class Check
14 | {
15 | public $lastErrorString = null;
16 | public $report = '';
17 |
18 | function check(): bool
19 | {
20 | $db = DBConnection::instance();
21 | $msg = [];
22 |
23 | // Task without list
24 | $count = $db->sq("SELECT COUNT(*) FROM {$db->prefix}todolist WHERE list_id NOT IN (SELECT id FROM {$db->prefix}lists)");
25 | if ($count) {
26 | $msg[] = "Tasks without list: $count";
27 | }
28 |
29 | // Tag without task (not a broblem)
30 | $count = $db->sq("SELECT COUNT(*) FROM {$db->prefix}tags WHERE id NOT IN (SELECT tag_id FROM {$db->prefix}tag2task)");
31 | if ($count) {
32 | $msg[] = "Tags without task: $count";
33 | }
34 |
35 | // tag2task no list
36 | $count = $db->sq("SELECT COUNT(*) FROM {$db->prefix}tag2task WHERE list_id NOT IN (SELECT id FROM {$db->prefix}lists)");
37 | if ($count) {
38 | $msg[] = "tag2task no list: $count";
39 | }
40 |
41 | // tag2task no tag
42 | $count = $db->sq("SELECT COUNT(*) FROM {$db->prefix}tag2task WHERE tag_id NOT IN (SELECT id FROM {$db->prefix}tags)");
43 | if ($count) {
44 | $msg[] = "tag2task no tag: $count";
45 | }
46 |
47 | // tag2task no task
48 | $count = $db->sq("SELECT COUNT(*) FROM {$db->prefix}tag2task WHERE task_id NOT IN (SELECT id FROM {$db->prefix}todolist)");
49 | if ($count) {
50 | $msg[] = "tag2task no task: $count";
51 | }
52 |
53 | $count = 0;
54 | $uniqTag = []; // lowerTag => [id, tag]
55 | $nonuniqTag = []; // id => [tag, lowerTag, uniqId, uniqTag, taskCount]
56 | $q = $db->dq("SELECT id,name,COUNT(task_id) c FROM {$db->prefix}tags t LEFT JOIN {$db->prefix}tag2task tt ON t.id=tt.tag_id GROUP BY id ORDER BY id");
57 | while ($r = $q->fetchAssoc()) {
58 | $v = mb_strtolower((string)$r['name'], 'UTF-8');
59 | if (!isset($uniqTag[$v])) {
60 | $uniqTag[$v] = [$r['id'], $r['name']];
61 | }
62 | else {
63 | $count++;
64 | $nonuniqTag[$r['id']] = [$r['name'], $v, $uniqTag[$v][0], $uniqTag[$v][1], $r['c']];
65 | }
66 | }
67 | if ($count > 0) {
68 | $msg[] = "Non-unique tags: $count";
69 | foreach ($nonuniqTag as $id => $a) {
70 | $msg[] = " ID:{$id} Tag:{$a[0]} (tasks: {$a[4]}) same as ID:{$a[2]} Tag:{$a[3]}";
71 | }
72 | }
73 |
74 | if (count($msg) == 0) {
75 | $msg[] = "OK";
76 | }
77 |
78 | $this->report = implode("\n", $msg);
79 | return true;
80 | }
81 |
82 | function repair(): bool
83 | {
84 | $db = DBConnection::instance();
85 |
86 | $db->ex("BEGIN");
87 |
88 | // Task without list
89 | $count = (int)$db->sq("SELECT COUNT(*) FROM {$db->prefix}todolist WHERE list_id NOT IN (SELECT id FROM {$db->prefix}lists)");
90 | if ($count > 0) {
91 | // Move to new list
92 | $listID = \DBCore::default()->createListWithName("Restored tasks");
93 | $db->ex("UPDATE {$db->prefix}todolist SET list_id=? WHERE list_id NOT IN (SELECT id FROM {$db->prefix}lists)", [$listID]);
94 | }
95 |
96 | //Tags
97 | $db->ex("DELETE FROM {$db->prefix}tags WHERE id NOT IN (SELECT tag_id FROM {$db->prefix}tag2task)");
98 | $db->ex("DELETE FROM {$db->prefix}tag2task WHERE task_id NOT IN (SELECT id FROM {$db->prefix}todolist)");
99 | $db->ex("DELETE FROM {$db->prefix}tag2task WHERE tag_id NOT IN (SELECT id FROM {$db->prefix}tags)");
100 |
101 | //Non-unique tags replace with first unique
102 | $uniqTag = [];
103 | $replace = [];
104 | $q = $db->dq("SELECT id,name FROM {$db->prefix}tags t LEFT JOIN {$db->prefix}tag2task tt ON t.id=tt.tag_id GROUP BY id ORDER BY id");
105 | while ($r = $q->fetchAssoc()) {
106 | $v = mb_strtolower((string)$r['name'], 'UTF-8');
107 | if (!isset($uniqTag[$v])) {
108 | $uniqTag[$v] = $r['id'];
109 | }
110 | else {
111 | $replace[$r['id']] = $uniqTag[$v];
112 | }
113 | }
114 | foreach ($replace as $id => $newId) {
115 | $db->ex("UPDATE {$db->prefix}tag2task SET tag_id=? WHERE tag_id=?", [$newId, $id]);
116 | }
117 | $db->ex("DELETE FROM {$db->prefix}tags WHERE id NOT IN (SELECT tag_id FROM {$db->prefix}tag2task)");
118 |
119 |
120 | // TODO: tag2task no list ?
121 |
122 | $db->ex("COMMIT");
123 | return true;
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/src/ext/notifications/class.telegramapi.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | namespace Notify;
10 |
11 | class TelegramApi
12 | {
13 | private $token = '';
14 | /** @var ?array $lastError */
15 | public $lastError = null;
16 | public $logApiErrors = false;
17 | public $throwExceptionOnApiError = false;
18 |
19 | function __construct(string $token)
20 | {
21 | $this->token = $token;
22 | }
23 |
24 | function getMe(): ?array
25 | {
26 | return $this->makeGetRequest('getMe');
27 | }
28 |
29 | function getUpdates(?array $params = null): ?array
30 | {
31 | return $this->makePostRequest('getUpdates', $params ?? []);
32 | }
33 |
34 | function sendMessage(array $params): ?array
35 | {
36 | return $this->makePostRequest('sendMessage', $params);
37 | }
38 |
39 | private function makeGetRequest(string $method): ?array
40 | {
41 | $options = array(
42 | 'http' => array(
43 | 'ignore_errors' => true
44 | )
45 | );
46 | $context = stream_context_create($options);
47 | $this->lastError = null;
48 | $body = $err = null;
49 | set_error_handler(function ($errno, $message, $file, $line) {
50 | throw new \ErrorException($message, $errno, $errno, $file, $line);
51 | });
52 | try {
53 | $body = @file_get_contents('https://api.telegram.org/bot'. $this->token .'/'. $method, false, $context);
54 | }
55 | catch (\Exception $e) {
56 | $err = boolval(ini_get('html_errors')) ? htmlspecialchars_decode($e->getMessage()) : $e->getMessage();
57 | }
58 | restore_error_handler();
59 | if ($body === false || null !== $err) {
60 | $msg = "Failed to make request to Telegram API ($method)". ($err ? ": $err" : "");
61 | if ($this->logApiErrors) {
62 | error_log($msg);
63 | }
64 | throw new \Exception($msg);
65 | }
66 | $decodedBody = $this->decodeBody($body, $method);
67 | return $decodedBody['result'] ?? [];
68 | }
69 |
70 | private function makePostRequest(string $method, array $params): ?array
71 | {
72 | $json = json_encode($params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
73 | $options = array(
74 | 'http' => array(
75 | 'header' => "Content-type: application/json\r\n",
76 | 'method' => 'POST',
77 | 'content' => $json,
78 | 'ignore_errors' => true
79 | )
80 | );
81 | $context = stream_context_create($options);
82 | $this->lastError = null;
83 | $body = $err = null;
84 | set_error_handler(function ($errno, $message, $file, $line) {
85 | throw new \ErrorException($message, $errno, $errno, $file, $line);
86 | });
87 | try {
88 | $body = @file_get_contents('https://api.telegram.org/bot'. $this->token .'/'. $method, false, $context);
89 | }
90 | catch (\Exception $e) {
91 | $err = boolval(ini_get('html_errors')) ? htmlspecialchars_decode($e->getMessage()) : $e->getMessage();
92 | }
93 | restore_error_handler();
94 | if ($body === false || null !== $err) {
95 | $msg = "Failed to make request to Telegram API ($method)". ($err ? ": $err" : "");
96 | if ($this->logApiErrors) {
97 | error_log($msg);
98 | }
99 | throw new \Exception($msg);
100 | }
101 | $decodedBody = $this->decodeBody($body, $method);
102 | return $decodedBody['result'] ?? [];
103 | }
104 |
105 | private function decodeBody(string $body, string $method = ''): array
106 | {
107 | $decodedBody = json_decode($body, true);
108 | if (!is_array($decodedBody)) {
109 | $decodedBody = [];
110 | }
111 | if (!isset($decodedBody['ok'])) {
112 | throw new \Exception("Telegram API ($method) Error");
113 | }
114 | if ($decodedBody['ok'] === false) {
115 | $this->lastError = [
116 | 'error_code' => $decodedBody['error_code'] ?? 0,
117 | 'description' => ($decodedBody['description'] ?? '')
118 | ];
119 | if ($this->logApiErrors) {
120 | error_log("Telegram API ($method) Error ". $this->lastError['error_code']. "): ". $this->lastError['description']);
121 | }
122 | if ($this->throwExceptionOnApiError) {
123 | throw new \Exception("Telegram API ($method) Error ". $this->lastError['error_code']. ": ". $this->lastError['description']);
124 | }
125 | }
126 | return $decodedBody;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/includes/lang/ja.json:
--------------------------------------------------------------------------------
1 | {
2 | "_header": {
3 | "ver": "v1.3.6",
4 | "date": "2010-12-17",
5 | "language": "Japanese",
6 | "original_name": "日本語",
7 | "author": "Calltella",
8 | "author_url": "http://calltella.com/"
9 | },
10 | "My Tiny Todolist": "My Tiny Todolist",
11 | "htab_newtask": "新規タスク",
12 | "htab_search": "検索",
13 | "btn_add": "追加",
14 | "btn_search": "検索",
15 | "advanced_add": "詳細追加",
16 | "searching": "検索中-",
17 | "tasks": "タスク",
18 | "taskdate_inline_created": "created at %s",
19 | "taskdate_inline_completed": "Completed at %s",
20 | "taskdate_inline_duedate": "Due %s",
21 | "taskdate_created": "作成時間",
22 | "taskdate_completed": "完了時間",
23 | "edit_task": "タスク編集",
24 | "add_task": "新規タスク",
25 | "priority": "優先度",
26 | "task": "タスク",
27 | "note": "詳細",
28 | "tags": "タグ",
29 | "save": "保存",
30 | "cancel": "キャンセル",
31 | "password": "パスワード",
32 | "btn_login": "ログイン",
33 | "a_login": "ログイン",
34 | "a_logout": "ログアウト",
35 | "public_tasks": "公開タスク",
36 | "tagcloud": "Tags",
37 | "tagfilter_cancel": "キャンセル",
38 | "sortByHand": "手動で並び替え",
39 | "sortByPriority": "優先度で並び替え",
40 | "sortByDueDate": "日付で並び替え",
41 | "sortByDateCreated": "Sort by date created",
42 | "sortByDateModified": "Sort by date modified",
43 | "due": "期限",
44 | "daysago": "%d 日経過",
45 | "indays": "あと %d 日",
46 | "months_short": [
47 | "1月",
48 | "2月",
49 | "3月",
50 | "4月",
51 | "5月",
52 | "6月",
53 | "7月",
54 | "8月",
55 | "9月",
56 | "10月",
57 | "11月",
58 | "12月"
59 | ],
60 | "months_long": [
61 | "January",
62 | "February",
63 | "March",
64 | "April",
65 | "May",
66 | "June",
67 | "July",
68 | "August",
69 | "September",
70 | "October",
71 | "November",
72 | "December"
73 | ],
74 | "days_min": [
75 | "日",
76 | "月",
77 | "火",
78 | "水",
79 | "木",
80 | "金",
81 | "土"
82 | ],
83 | "days_long": [
84 | "日曜日",
85 | "月曜日",
86 | "火曜日",
87 | "水曜日",
88 | "木曜日",
89 | "金曜日",
90 | "土曜日"
91 | ],
92 | "today": "今日",
93 | "yesterday": "昨日",
94 | "tomorrow": "明日",
95 | "f_past": "期限切れ",
96 | "f_today": "今日と明日",
97 | "f_soon": "もうすぐ",
98 | "action_edit": "編集",
99 | "action_note": "ノート編集",
100 | "action_delete": "削除",
101 | "action_priority": "優先度",
102 | "action_move": "移動先",
103 | "notes": "詳細:",
104 | "notes_show": "表示",
105 | "notes_hide": "非表示",
106 | "list_new": "新規リスト",
107 | "list_rename": "リスト名変更",
108 | "list_delete": "リスト削除",
109 | "list_publish": "公開リスト",
110 | "list_showcompleted": "完了済みタスクを表示",
111 | "list_clearcompleted": "完了済みタスクをクリア",
112 | "list_select": "Select list",
113 | "list_export": "Export",
114 | "list_export_csv": "CSV",
115 | "list_export_ical": "iCalendar",
116 | "list_rssfeed": "RSS Feed",
117 | "alltags": "全てのタグ:",
118 | "alltags_show": "全表示",
119 | "alltags_hide": "全非表示",
120 | "a_settings": "設定",
121 | "rss_feed": "RSSフィード",
122 | "feed_title": "%s",
123 | "feed_completed_tasks": "Completed tasks",
124 | "feed_modified_tasks": "Modified tasks",
125 | "feed_new_tasks": "New tasks",
126 | "alltasks": "All tasks",
127 | "set_header": "設定",
128 | "set_title": "タイトル",
129 | "set_title_descr": "(指定の無い場合はデフォルトのタイトルを使用します。)",
130 | "set_language": "言語",
131 | "set_protection": "パスワード保護",
132 | "set_enabled": "有効",
133 | "set_disabled": "無効",
134 | "set_newpass": "新規パスワード",
135 | "set_newpass_descr": "(空白の場合はパスワード変更されません。)",
136 | "set_smartsyntax": "Smart syntax",
137 | "set_smartsyntax_descr": "(/priority/ task /tags/)",
138 | "set_timezone": "Time zone",
139 | "set_autotag": "自動タグ設定",
140 | "set_autotag_descr": "(タスクフィルターしている場合は自動的にタグを挿入します。)",
141 | "set_sessions": "セッション処理",
142 | "set_sessions_php": "PHP",
143 | "set_sessions_files": "Files",
144 | "set_firstdayofweek": "週の始まり",
145 | "set_custom": "Custom",
146 | "set_date": "日付フォーマット",
147 | "set_date2": "Short Date format",
148 | "set_shortdate": "短縮日付フォーマット",
149 | "set_clock": "時刻フォーマット",
150 | "set_12hour": "12時間表示",
151 | "set_24hour": "24時間表示",
152 | "set_submit": "設定変更",
153 | "set_cancel": "キャンセル",
154 | "set_showdate": "タスクに日付を表示",
155 | "confirmDelete": "タスクを削除してもよろしいですか?",
156 | "confirmLeave": "There can be unsaved data. Do you really want to leave?",
157 | "actionNoteSave": "保存",
158 | "actionNoteCancel": "キャンセル",
159 | "error": "エラーが発生しました。 (クリックで詳細)",
160 | "denied": "アクセスが拒否されました。",
161 | "invalidpass": "パスワードが違います。",
162 | "addList": "新規リスト作成",
163 | "addListDefault": "Todo",
164 | "renameList": "リスト名変更",
165 | "deleteList": "全てのタスクと現在のリストを削除します。\nよろしいですか?",
166 | "clearCompleted": "完了した全てのリストを削除します。\nよろしいですか?",
167 | "settingsSaved": "設定保存中..."
168 | }
169 |
--------------------------------------------------------------------------------
/src/includes/lang/he.json:
--------------------------------------------------------------------------------
1 | {
2 | "_header": {
3 | "ver": "v1.3.6",
4 | "date": "2010-08-01",
5 | "language": "Hebrew",
6 | "original_name": "עברית",
7 | "author": "Ohad Raz",
8 | "author_url": "http://www.Bainternet.info",
9 | "rtl": 1
10 | },
11 | "My Tiny Todolist": "My Tiny Todolist",
12 | "htab_newtask": "משימה חדשה",
13 | "htab_search": "חיפוש",
14 | "btn_add": "הוספה",
15 | "btn_search": "לחפש",
16 | "advanced_add": "הוספה מתקדמת",
17 | "searching": "מחפש ",
18 | "tasks": "משימות",
19 | "taskdate_inline_created": "created at %s",
20 | "taskdate_inline_completed": "Completed at %s",
21 | "taskdate_inline_duedate": "Due %s",
22 | "taskdate_created": "תאריך היצירה",
23 | "taskdate_completed": "תאריך סיום",
24 | "edit_task": "ערוך משימה",
25 | "add_task": "הוסף משימה",
26 | "priority": "עדיפות",
27 | "task": "משימה",
28 | "note": "פרטים",
29 | "tags": "ליבלים",
30 | "save": "שמור",
31 | "cancel": "בטל",
32 | "password": "סיסמה",
33 | "btn_login": "התחבר",
34 | "a_login": "התחבר",
35 | "a_logout": "התנתק",
36 | "public_tasks": "משימות פתוחות לציבור",
37 | "tagcloud": "Tags",
38 | "tagfilter_cancel": "הסר מסנן",
39 | "sortByHand": "סדר לפי רשימה",
40 | "sortByPriority": "סדר לפי עדיפות",
41 | "sortByDueDate": "סדר לפי תאריך סיום",
42 | "sortByDateCreated": "Sort by date created",
43 | "sortByDateModified": "Sort by date modified",
44 | "due": "יש לסיים עד",
45 | "daysago": "% d ימים לפני",
46 | "indays": "עודב% d ימים",
47 | "months_short": [
48 | "Jan",
49 | "Feb",
50 | "Mar",
51 | "Apr",
52 | "May",
53 | "Jun",
54 | "July",
55 | "Aug",
56 | "Sep",
57 | "Oct",
58 | "Nov",
59 | "Dec"
60 | ],
61 | "months_long": [
62 | "ינואר",
63 | "פברואר",
64 | "מרץ",
65 | "אפריל",
66 | "מאי",
67 | "יוני",
68 | "יולי",
69 | "אוגוסט",
70 | "ספטמבר",
71 | "אוקטובר",
72 | "נובמבר",
73 | "דצמבר"
74 | ],
75 | "days_min": [
76 | "א",
77 | "ב",
78 | "ג",
79 | "ד",
80 | "ה",
81 | "ו",
82 | "ש"
83 | ],
84 | "days_long": [
85 | "ראשון",
86 | "שני",
87 | "שלישי",
88 | "רביעי",
89 | "חמישי",
90 | "שישי",
91 | "שבת"
92 | ],
93 | "today": "היום",
94 | "yesterday": "אתמול",
95 | "tomorrow": "מחר",
96 | "f_past": "מאוחר",
97 | "f_today": "היום ומחר",
98 | "f_soon": "בקרוב",
99 | "action_edit": "ערוך",
100 | "action_note": "ערוך פתק",
101 | "action_delete": "מחק",
102 | "action_priority": "עדיפות",
103 | "action_move": "הזז",
104 | "notes": "פתקים:",
105 | "notes_show": "הצג",
106 | "notes_hide": "הסתר",
107 | "list_new": "רשימה חדשה",
108 | "list_rename": "שנה שם",
109 | "list_delete": "מחק רשימה",
110 | "list_publish": "פרסם רשימה",
111 | "list_showcompleted": "הצג משימות שהסתימו",
112 | "list_clearcompleted": "הסר משימות שהסתימו",
113 | "list_select": "Select list",
114 | "list_export": "Export",
115 | "list_export_csv": "CSV",
116 | "list_export_ical": "iCalendar",
117 | "list_rssfeed": "RSS Feed",
118 | "alltags": "הכל:",
119 | "alltags_show": "הצג הכל",
120 | "alltags_hide": "הסתר",
121 | "a_settings": "הגדרות",
122 | "rss_feed": "פיד RSS",
123 | "feed_title": "%s",
124 | "feed_completed_tasks": "Completed tasks",
125 | "feed_modified_tasks": "Modified tasks",
126 | "feed_new_tasks": "New tasks",
127 | "alltasks": "All tasks",
128 | "set_header": "הגדרות",
129 | "set_title": "שם",
130 | "set_title_descr": "(ציין אם אתה רוצה לשנות את הכותרת כברירת מחדל)",
131 | "set_language": "שפה",
132 | "set_protection": "סגור בסיסמה",
133 | "set_enabled": "כן",
134 | "set_disabled": "לא",
135 | "set_newpass": "סיסמה חדשה",
136 | "set_newpass_descr": "(השאר ריק אם אתה לא לשנות את הסיסמה הנוכחית)",
137 | "set_smartsyntax": "תחביר מתקדם",
138 | "set_smartsyntax_descr": "(/ עדיפות / משימה / תגיות /)",
139 | "set_timezone": "Time zone",
140 | "set_autotag": "תיוג אוטומטי",
141 | "set_autotag_descr": "(הוספת מסנן התג אוטומטית של תוויות הנוכחי, המשימה האחרונה נוצר)",
142 | "set_sessions": "ניהול הפעלות",
143 | "set_sessions_php": "PHP",
144 | "set_sessions_files": "קבצים",
145 | "set_firstdayofweek": "יום ראשון של השבוע",
146 | "set_custom": "Custom",
147 | "set_date": "תאריך",
148 | "set_date2": "Short Date format",
149 | "set_shortdate": "תאריך מקוצר",
150 | "set_clock": "שעון",
151 | "set_12hour": "12-שעות",
152 | "set_24hour": "24-שעות",
153 | "set_submit": "שמור שינויים",
154 | "set_cancel": "בטל",
155 | "set_showdate": "הצג את התאריך של הפעילות ברשימה",
156 | "confirmDelete": "האם אתה בטוח למחוק את המשימה?",
157 | "confirmLeave": "There can be unsaved data. Do you really want to leave?",
158 | "actionNoteSave": "אשר",
159 | "actionNoteCancel": "בטל",
160 | "error": "אירעה שגיאה (לחץ כדי להציג פרטים)",
161 | "denied": "הגישה נדחתה",
162 | "invalidpass": "סיסמה שגויה",
163 | "addList": "יצירת רשימה",
164 | "addListDefault": "Todo",
165 | "renameList": "שינוי שם הרשימה",
166 | "deleteList": "זה יבטל את הרשימה הנוכחית, ואת המשימות שהיא מכילה. \nהאם אתה בטוח?",
167 | "clearCompleted": "פעולה זו תמחק את כל המשימות שהושלמו ברשימה. \nהאם אתה בטוח?",
168 | "settingsSaved": "הגדרות שנשמרו. טוען ..."
169 | }
170 |
--------------------------------------------------------------------------------
/src/ext/updater/class.updater.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | namespace UpdaterExtension;
10 |
11 | class Updater
12 | {
13 | public $lastErrorString = null;
14 |
15 | public function requestJson(string $url): ?string
16 | {
17 | $options = array(
18 | 'http' => array(
19 | 'header' => "Content-type: application/json\r\nUser-Agent: mytinytodo\r\n"
20 | )
21 | );
22 | $context = stream_context_create($options);
23 | set_error_handler(function ($errno, $message, $file, $line) {
24 | throw new \ErrorException($message, $errno, $errno, $file, $line);
25 | });
26 | $json = null;
27 | $this->lastErrorString = null;
28 | try {
29 | $json = @file_get_contents($url, false, $context);
30 | }
31 | catch (\Exception $e) {
32 | $this->lastErrorString = boolval(ini_get('html_errors')) ? htmlspecialchars_decode($e->getMessage()) : $e->getMessage();
33 | }
34 | restore_error_handler();
35 | if ($json === false) {
36 | return null;
37 | }
38 | return $json;
39 | }
40 |
41 | public function lastVersionInfo(): ?array
42 | {
43 | $json = $this->requestJson("https://api.github.com/repos/maxpozdeev/mytinytodo/releases");
44 | if ($json === null || $json == '') {
45 | error_log("Failed to request releases info: ".$this->lastErrorString);
46 | return null;
47 | }
48 | $releases = json_decode($json, true) ?? [];
49 |
50 | $a = null;
51 | foreach ($releases as $rel) {
52 | // find only stable
53 | if ($rel['prerelease'] ?? false) {
54 | continue;
55 | }
56 | $ver = substr($rel['tag_name'] ?? 'v', 1);
57 | if ($ver == '') continue;
58 | if ($a && version_compare($a['__ver'], $ver)) {
59 | continue; // skip lower version
60 | }
61 | $rel['__ver'] = $ver;
62 | $a = $rel;
63 | }
64 | if (null === $a) {
65 | $this->lastErrorString = "No release to update to";
66 | return null;
67 | }
68 |
69 | $ret = [];
70 | $ver = '';
71 | if (isset($a['tag_name'])) {
72 | $ver = substr($a['tag_name'], 1); //remove first 'v'
73 | }
74 | if ($ver != '' && isset($a['assets']) &&
75 | is_array($a['assets']) && count($a['assets']) > 0 &&
76 | ($asset = $a['assets'][0]) && isset($asset['browser_download_url']) )
77 | {
78 | $ret['version'] = $ver;
79 | $ret['download'] = $asset['browser_download_url'];
80 | }
81 | else {
82 | error_log("HTTP response contains unexpected content");
83 | $this->lastErrorString = "HTTP response contains unexpected content";
84 | }
85 | return $ret;
86 | }
87 |
88 | public function download(string $url, string $outfile): bool
89 | {
90 | $this->lastErrorString = null;
91 | $dir = dirname($outfile);
92 | if (!is_dir($dir) || !is_writable($dir)) {
93 | $this->lastErrorString = "myTinyTodo directory is not writable";
94 | return false;
95 | }
96 | $f = @fopen($url, 'r');
97 | if ($f === false) {
98 | $ea = error_get_last();
99 | $this->lastErrorString = $ea['message'] ?? "Failed to open stream";
100 | return false;
101 | }
102 | $bytes = @file_put_contents($outfile, $f, LOCK_EX);
103 | $ea = error_get_last();
104 | fclose($f);
105 | if ($bytes === false) {
106 | $this->lastErrorString = $ea['message'] ?? "Can not save file";
107 | return false;
108 | }
109 | return true;
110 | }
111 |
112 | public function extractAndReplace(string $filename): bool
113 | {
114 | $this->lastErrorString = null;
115 | $dir = MTTPATH;
116 | if (!is_dir($dir) || !is_writable($dir)) {
117 | $this->lastErrorString = "myTinyTodo directory is not writable";
118 | return false;
119 | }
120 |
121 | $output = null;
122 | $retval = null;
123 | $command = "tar xzf ". escapeshellarg($filename). " --strip-components 1 -C ". escapeshellarg($dir). " 2>&1";
124 | @exec($command, $output, $retval);
125 | if ($retval != 0) {
126 | $this->lastErrorString = "Failed to execute tar command ($retval): ". ($output ? implode("\n", $output) : "no output");
127 | error_log($this->lastErrorString);
128 | return false;
129 | }
130 |
131 | // Extensions
132 | $dir = MTT_EXT;
133 | $filename = $dir . 'extensions.tar.gz';
134 | if (file_exists($filename)) {
135 | if (!is_writable($dir)) {
136 | $this->lastErrorString = "Extensions directory is not writable";
137 | return false;
138 | }
139 | $command = "tar xzf ". escapeshellarg($filename). " -C ". escapeshellarg($dir). " 2>&1";
140 | @exec($command, $output, $retval);
141 | if ($retval != 0) {
142 | $this->lastErrorString = "Extensions: failed to execute tar command ($retval): ". ($output ? implode("\n", $output) : "no output");
143 | error_log($this->lastErrorString);
144 | return false;
145 | }
146 | unlink($filename);
147 | }
148 |
149 | return true;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/includes/lang/sv.json:
--------------------------------------------------------------------------------
1 | {
2 | "_header": {
3 | "ver": "v1.3.4",
4 | "date": "2010-05-25",
5 | "language": "Swedish",
6 | "original_name": "Svenska",
7 | "author": "Martin Danielsson"
8 | },
9 | "My Tiny Todolist": "My Tiny Todolist",
10 | "htab_newtask": "Ny uppgift",
11 | "htab_search": "Sök",
12 | "btn_add": "Lägg till",
13 | "btn_search": "Sök",
14 | "advanced_add": "Avancerat",
15 | "searching": "Söker efter",
16 | "tasks": "Uppgifter",
17 | "taskdate_inline_created": "created at %s",
18 | "taskdate_inline_completed": "Completed at %s",
19 | "taskdate_inline_duedate": "Due %s",
20 | "taskdate_created": "Skapad",
21 | "taskdate_completed": "Avslutad",
22 | "edit_task": "Ändra uppgift",
23 | "add_task": "Ny uppgift",
24 | "priority": "Prioritet",
25 | "task": "Uppgift",
26 | "note": "Notering",
27 | "tags": "Taggar",
28 | "save": "Spara",
29 | "cancel": "Avbryt",
30 | "password": "Lösenord",
31 | "btn_login": "Logga in",
32 | "a_login": "Logga in",
33 | "a_logout": "Logga ut",
34 | "public_tasks": "Allmänna uppgifter",
35 | "tagcloud": "Tags",
36 | "tagfilter_cancel": "ta bort filter",
37 | "sortByHand": "Sortera för hand",
38 | "sortByPriority": "Sortera efter prioritet",
39 | "sortByDueDate": "Sortera efter deadline",
40 | "sortByDateCreated": "Sort by date created",
41 | "sortByDateModified": "Sort by date modified",
42 | "due": "Deadline",
43 | "daysago": "%d dagar sen",
44 | "indays": "om %d dagar",
45 | "months_short": [
46 | "Jan",
47 | "Feb",
48 | "Mar",
49 | "Apr",
50 | "Maj",
51 | "Jun",
52 | "Jul",
53 | "Aug",
54 | "Sep",
55 | "Okt",
56 | "Nov",
57 | "Dec"
58 | ],
59 | "months_long": [
60 | "Januari",
61 | "Februari",
62 | "Mars",
63 | "April",
64 | "Maj",
65 | "Juni",
66 | "July",
67 | "Augusti",
68 | "September",
69 | "Oktober",
70 | "November",
71 | "December"
72 | ],
73 | "days_min": [
74 | "Sö",
75 | "Må",
76 | "Ti",
77 | "On",
78 | "To",
79 | "Fr",
80 | "Lö"
81 | ],
82 | "days_long": [
83 | "Söndag",
84 | "Månday",
85 | "Tisdag",
86 | "Onsdag",
87 | "Torsdag",
88 | "Fredag",
89 | "Lördag"
90 | ],
91 | "today": "idag",
92 | "yesterday": "igår",
93 | "tomorrow": "imorgon",
94 | "f_past": "Försenad",
95 | "f_today": "Idag och imorgon",
96 | "f_soon": "Snart",
97 | "action_edit": "Ändra",
98 | "action_note": "Ändra notering",
99 | "action_delete": "Ta bort",
100 | "action_priority": "Prioritet",
101 | "action_move": "Flytta till",
102 | "notes": "Noteringar:",
103 | "notes_show": "Visa",
104 | "notes_hide": "Dölj",
105 | "list_new": "Ny lista",
106 | "list_rename": "Döp om lista",
107 | "list_delete": "Ta bort lista",
108 | "list_publish": "Publicera lista",
109 | "list_showcompleted": "Visa avslutade uppgifter",
110 | "list_clearcompleted": "Töm avslutade uppgifter",
111 | "list_select": "Select list",
112 | "list_export": "Export",
113 | "list_export_csv": "CSV",
114 | "list_export_ical": "iCalendar",
115 | "list_rssfeed": "RSS Feed",
116 | "alltags": "Alla taggar:",
117 | "alltags_show": "Visa alla",
118 | "alltags_hide": "Dölj alla",
119 | "a_settings": "Inställningar",
120 | "rss_feed": "RSS-flöde",
121 | "feed_title": "%s",
122 | "feed_completed_tasks": "Completed tasks",
123 | "feed_modified_tasks": "Modified tasks",
124 | "feed_new_tasks": "New tasks",
125 | "alltasks": "All tasks",
126 | "set_header": "Inställningar",
127 | "set_title": "Titel",
128 | "set_title_descr": "(specifiera om du vill ändra standardtitel)",
129 | "set_language": "Språk",
130 | "set_protection": "Lösenordsskydd",
131 | "set_enabled": "På",
132 | "set_disabled": "Av",
133 | "set_newpass": "Nytt lösenord",
134 | "set_newpass_descr": "(lämna blank för att inte byta lösenord)",
135 | "set_smartsyntax": "Smart syntax",
136 | "set_smartsyntax_descr": "(/prioritet/ uppgift /taggar/)",
137 | "set_timezone": "Time zone",
138 | "set_autotag": "Autotaggning",
139 | "set_autotag_descr": "(lägger automatiskt till taggar för det aktuella filtret när man skapar nya uppgifter)",
140 | "set_sessions": "Hantering av sessioner",
141 | "set_sessions_php": "PHP",
142 | "set_sessions_files": "Filer",
143 | "set_firstdayofweek": "Vilken veckodag börjar veckan på",
144 | "set_custom": "Custom",
145 | "set_date": "Datumformat",
146 | "set_date2": "Short Date format",
147 | "set_shortdate": "Kort datumformat",
148 | "set_clock": "Tidsformat",
149 | "set_12hour": "12-timmars",
150 | "set_24hour": "24-timmars",
151 | "set_submit": "Spara ändringar",
152 | "set_cancel": "Avbryt",
153 | "set_showdate": "Visa datum i listan",
154 | "confirmDelete": "Vill du verkligen radera den här uppgiften?",
155 | "confirmLeave": "There can be unsaved data. Do you really want to leave?",
156 | "actionNoteSave": "spara",
157 | "actionNoteCancel": "avbryt",
158 | "error": "Ett fel har uppstått (Tryck för mer info)",
159 | "denied": "Tillträde nekas",
160 | "invalidpass": "Fel lösenord",
161 | "addList": "Skapa ny lista",
162 | "addListDefault": "Todo",
163 | "renameList": "Döp om lista",
164 | "deleteList": "Det här tar bort listan och alla uppgifter.\nVill du fortsätta?",
165 | "clearCompleted": "Det här tar bort alla avslutade uppgifter.\nFortsätt?",
166 | "settingsSaved": "Inställningar sparade. Laddar om..."
167 | }
168 |
--------------------------------------------------------------------------------
/src/includes/lang/th.json:
--------------------------------------------------------------------------------
1 | {
2 | "_header": {
3 | "ver": "v1.3.4",
4 | "date": "2010-11-15",
5 | "language": "Thai",
6 | "original_name": "ไทย",
7 | "author": "Maxasus123",
8 | "author_url": "http://www.bob.in.th"
9 | },
10 | "My Tiny Todolist": "Todolist ของฉัน",
11 | "htab_newtask": "งานใหม่",
12 | "htab_search": "ค้นหา",
13 | "btn_add": "เพิ่ม",
14 | "btn_search": "ค้นหา",
15 | "advanced_add": "ขั้นสูง",
16 | "searching": "การค้นหา",
17 | "tasks": "งาน",
18 | "taskdate_inline_created": "created at %s",
19 | "taskdate_inline_completed": "Completed at %s",
20 | "taskdate_inline_duedate": "Due %s",
21 | "taskdate_created": "วันที่สร้าง",
22 | "taskdate_completed": "วันที่เสร็จสิ้น",
23 | "edit_task": "แก้ไขงาน",
24 | "add_task": "เพิ่มงานใหม่",
25 | "priority": "ลำดับความสำคัญ",
26 | "task": "งาน",
27 | "note": "Note",
28 | "tags": "แท็ก",
29 | "save": "บันทึก",
30 | "cancel": "ยกเลิก",
31 | "password": "รหัสผ่าน",
32 | "btn_login": "เข้าสู่ระบบ",
33 | "a_login": "เข้าสู่ระบบ",
34 | "a_logout": "ออกจากระบบ",
35 | "public_tasks": "Public Tasks",
36 | "tagcloud": "Tags",
37 | "tagfilter_cancel": "ยกเลิกการกรอง",
38 | "sortByHand": "เรียงด้วยมือ",
39 | "sortByPriority": "เรียงตามลำดับความสำคัญ",
40 | "sortByDueDate": "เรียงตามวันที่กำหนด",
41 | "sortByDateCreated": "Sort by date created",
42 | "sortByDateModified": "Sort by date modified",
43 | "due": "ครบกำหนา",
44 | "daysago": "%d วันที่ผ่านมา",
45 | "indays": "ใน %d วัน",
46 | "months_short": [
47 | "ม.ค.",
48 | "ก.พ.",
49 | "มี.ค.",
50 | "เม.ย.",
51 | "พ.ค.",
52 | "มิ.ย.",
53 | "ก.ค.",
54 | "ส.ค.",
55 | "ก.ย.",
56 | "ต.ค.",
57 | "พ.ย.",
58 | "ธ.ค."
59 | ],
60 | "months_long": [
61 | "มกราคม",
62 | "กุมภาพันธ์",
63 | "มีนาคม",
64 | "เมษายน",
65 | "พฤษภาคม",
66 | "มิถุนายน",
67 | "กรกฎาคม",
68 | "สิงหาคม",
69 | "กันยายน",
70 | "ตุลาคม",
71 | "พฤศจิกายน",
72 | "ธันวาคม"
73 | ],
74 | "days_min": [
75 | "อา.",
76 | "จ.",
77 | "อ.",
78 | "พ.",
79 | "พฤ.",
80 | "ศ.",
81 | "ส."
82 | ],
83 | "days_long": [
84 | "อาทิตย์",
85 | "จันทร์",
86 | "อังคาร",
87 | "พุธ",
88 | "พฤหัสบดี",
89 | "ศุกร์",
90 | "เสาร์"
91 | ],
92 | "today": "วันนี้",
93 | "yesterday": "เมื่อวาน",
94 | "tomorrow": "พรุ่งนี้",
95 | "f_past": "เกินกำหนด",
96 | "f_today": "วันนี้และวันพรุ่งนี้",
97 | "f_soon": "ในไม่ช้า",
98 | "action_edit": "แก้ไข",
99 | "action_note": "แก้ไข Note",
100 | "action_delete": "ลบ",
101 | "action_priority": "ลำดับความสำคัญ",
102 | "action_move": "ย้ายไป",
103 | "notes": "Notes:",
104 | "notes_show": "โชว์",
105 | "notes_hide": "ซ่อน",
106 | "list_new": "รายการใหม่",
107 | "list_rename": "เปลี่ยนชื่อรายการ",
108 | "list_delete": "ลบรายการ",
109 | "list_publish": "เผยแพร่รายการ",
110 | "list_showcompleted": "แสดงงานที่เสร็จแล้ว",
111 | "list_clearcompleted": "Clear งานที่เสร็จแล้ว",
112 | "list_select": "Select list",
113 | "list_export": "Export",
114 | "list_export_csv": "CSV",
115 | "list_export_ical": "iCalendar",
116 | "list_rssfeed": "RSS Feed",
117 | "alltags": "แท็กทั้งหมด:",
118 | "alltags_show": "โชว์ ทั้งหมด",
119 | "alltags_hide": "ซ่อนทั้งหม",
120 | "a_settings": "การตั้งค่า",
121 | "rss_feed": "RSS Feed",
122 | "feed_title": "%s",
123 | "feed_completed_tasks": "Completed tasks",
124 | "feed_modified_tasks": "Modified tasks",
125 | "feed_new_tasks": "New tasks",
126 | "alltasks": "All tasks",
127 | "set_header": "การตั้งค่า",
128 | "set_title": "ชื่อเรื่อง",
129 | "set_title_descr": "(ระบุหากคุณต้องการเปลี่ยนชื่อเรื่องเริ่มต้น)",
130 | "set_language": "ภาษา",
131 | "set_protection": "รหัสป้องกัน",
132 | "set_enabled": "เปิดใช้งาน",
133 | "set_disabled": "ปิดใช้งาน",
134 | "set_newpass": "รหัสผ่านใหม่",
135 | "set_newpass_descr": "(เว้นว่างไว้หากจะไม่มีการเปลี่ยนแปลงรหัสผ่านปัจจุบัน)",
136 | "set_smartsyntax": "Smart syntax",
137 | "set_smartsyntax_descr": "(/ลำดับความสำคัญ/ งาน /แท็ก/)",
138 | "set_timezone": "Time zone",
139 | "set_autotag": "Autotagging",
140 | "set_autotag_descr": "(โดยอัตโนมัติเพิ่มแท็กแท็กของตัวกรองปัจจุบันกับงานที่สร้างขึ้นใหม่)",
141 | "set_sessions": "Session handling mechanism",
142 | "set_sessions_php": "PHP",
143 | "set_sessions_files": "ไฟล์",
144 | "set_firstdayofweek": "วันแรกของสัปดาห์",
145 | "set_custom": "Custom",
146 | "set_date": "รูปแบบวันที่",
147 | "set_date2": "Short Date format",
148 | "set_shortdate": "วันที่แบบย่อ",
149 | "set_clock": "รูปแบบเวลา",
150 | "set_12hour": "12 ชั่วโมง",
151 | "set_24hour": "24 ชั่วโมง",
152 | "set_submit": "บันทึก",
153 | "set_cancel": "ยกเลิก",
154 | "set_showdate": "วันที่งานแสดงในรายการ",
155 | "confirmDelete": "คุณแน่ใจหรือว่าต้องการลบงาน?",
156 | "confirmLeave": "There can be unsaved data. Do you really want to leave?",
157 | "actionNoteSave": "บันทึก",
158 | "actionNoteCancel": "ยกเลิก",
159 | "error": "ข้อผิดพลาดบางอย่างเกิดขึ้น (คลิกเพื่อดูรายละเอียด)",
160 | "denied": "ปฏิเสธการเข้าใช้",
161 | "invalidpass": "รหัสผ่านผิด",
162 | "addList": "การสร้างรายการใหม่",
163 | "addListDefault": "Todo",
164 | "renameList": "เปลี่ยนชื่อรายการใหม่",
165 | "deleteList": "นี้จะลบรายการปัจจุบันกับงานทั้งหมดในนั้น. \nคุณแน่ใจหรือไม่?",
166 | "clearCompleted": "นี้จะลบรายการที่ทำเสร็จทั้งหมดในรายการ\nคุณแน่ใจหรือไม่?",
167 | "settingsSaved": "บันทึกการตั้งค่า โหลด ..."
168 | }
169 |
--------------------------------------------------------------------------------
/src/includes/lang/mk.json:
--------------------------------------------------------------------------------
1 | {
2 | "_header": {
3 | "ver": "v1.3.3",
4 | "date": "2010-02-11",
5 | "language": "Macedonian",
6 | "original_name": "Македонски",
7 | "author": "nGen Solutions",
8 | "author_url": "http://ngen.mk"
9 | },
10 | "My Tiny Todolist": "Листа на Задачи",
11 | "htab_newtask": "Нова задача",
12 | "htab_search": "Пребарај",
13 | "btn_add": "Додади",
14 | "btn_search": "Барај",
15 | "advanced_add": "Напредно",
16 | "searching": "Пребарувам за",
17 | "tasks": "Задачи",
18 | "taskdate_inline_created": "created at %s",
19 | "taskdate_inline_completed": "Completed at %s",
20 | "taskdate_inline_duedate": "Due %s",
21 | "taskdate_created": "Креирана",
22 | "taskdate_completed": "Завршена",
23 | "edit_task": "Измени задача",
24 | "add_task": "Нова Задача",
25 | "priority": "Приоритет",
26 | "task": "Задача",
27 | "note": "Забелешка",
28 | "tags": "Ознаки",
29 | "save": "Сочувај",
30 | "cancel": "Откажи",
31 | "password": "Лозинка",
32 | "btn_login": "Најави се",
33 | "a_login": "Најава",
34 | "a_logout": "Одјави се",
35 | "public_tasks": "Јавни задачи",
36 | "tagcloud": "Tags",
37 | "tagfilter_cancel": "откажи филтер",
38 | "sortByHand": "Подреди рачно",
39 | "sortByPriority": "Подреди по приоритет",
40 | "sortByDueDate": "Подреди по рок",
41 | "sortByDateCreated": "Sort by date created",
42 | "sortByDateModified": "Sort by date modified",
43 | "due": "Рок",
44 | "daysago": "пред %d денови",
45 | "indays": "за %d денови",
46 | "months_short": [
47 | "Јан",
48 | "Фев",
49 | "Мар",
50 | "Апр",
51 | "Мај",
52 | "Јун",
53 | "Јул",
54 | "Авг",
55 | "Сеп",
56 | "Окт",
57 | "Нов",
58 | "Дек"
59 | ],
60 | "months_long": [
61 | "Јануари",
62 | "Февруари",
63 | "Март",
64 | "Април",
65 | "Мај",
66 | "Јуни",
67 | "Јули",
68 | "Август",
69 | "Септември",
70 | "Октомври",
71 | "Ноември",
72 | "Декември"
73 | ],
74 | "days_min": [
75 | "Не",
76 | "По",
77 | "Вт",
78 | "Ср",
79 | "Че",
80 | "Пе",
81 | "Са"
82 | ],
83 | "days_long": [
84 | "Недела",
85 | "Понеделник",
86 | "Вторник",
87 | "Среда",
88 | "Четврток",
89 | "Петок",
90 | "Сабота"
91 | ],
92 | "today": "денес",
93 | "yesterday": "вчера",
94 | "tomorrow": "утре",
95 | "f_past": "Задоцнети",
96 | "f_today": "денес и утре",
97 | "f_soon": "наскоро",
98 | "action_edit": "Измени",
99 | "action_note": "Измени забелешка",
100 | "action_delete": "Избриши",
101 | "action_priority": "Приоритет",
102 | "action_move": "Премести во",
103 | "notes": "Забелешки:",
104 | "notes_show": "Прикажи",
105 | "notes_hide": "Сокриј",
106 | "list_new": "Нова листа",
107 | "list_rename": "Преименувај листа",
108 | "list_delete": "Избриши листа",
109 | "list_publish": "Објави листа",
110 | "list_showcompleted": "Покажи завршени задачи",
111 | "list_clearcompleted": "Clear completed tasks",
112 | "list_select": "Select list",
113 | "list_export": "Export",
114 | "list_export_csv": "CSV",
115 | "list_export_ical": "iCalendar",
116 | "list_rssfeed": "RSS Feed",
117 | "alltags": "Сите ознаки:",
118 | "alltags_show": "Прикажи ги сите",
119 | "alltags_hide": "Сокриј ги сите",
120 | "a_settings": "Подесувања",
121 | "rss_feed": "RSS достава",
122 | "feed_title": "%s",
123 | "feed_completed_tasks": "Completed tasks",
124 | "feed_modified_tasks": "Modified tasks",
125 | "feed_new_tasks": "New tasks",
126 | "alltasks": "All tasks",
127 | "set_header": "Подесувања",
128 | "set_title": "Наслов",
129 | "set_title_descr": "(промени го името на програмата)",
130 | "set_language": "Јазик",
131 | "set_protection": "Заштита со лозинка",
132 | "set_enabled": "Вклучи",
133 | "set_disabled": "Исклучи",
134 | "set_newpass": "Нова лозинка",
135 | "set_newpass_descr": "(празно нема да ја смени лозинката)",
136 | "set_smartsyntax": "Паметен приказ",
137 | "set_smartsyntax_descr": "(/приоритет/ задача /ознаки/)",
138 | "set_timezone": "Time zone",
139 | "set_autotag": "Автоматски ознаки",
140 | "set_autotag_descr": "(автоматски ја додава филтрираната ознака на новокреираната задача)",
141 | "set_sessions": "Механизам за справување со сесијата",
142 | "set_sessions_php": "PHP",
143 | "set_sessions_files": "Со Фајлови",
144 | "set_firstdayofweek": "Прв ден од неделата",
145 | "set_custom": "Custom",
146 | "set_date": "Приказ на дата",
147 | "set_date2": "Short Date format",
148 | "set_shortdate": "Краток формат",
149 | "set_clock": "Приказ на време",
150 | "set_12hour": "12-часа",
151 | "set_24hour": "24-часа",
152 | "set_submit": "Зачувај ги промените",
153 | "set_cancel": "Откажи",
154 | "set_showdate": "Show task date in list",
155 | "confirmDelete": "Дали си сигурен?",
156 | "confirmLeave": "There can be unsaved data. Do you really want to leave?",
157 | "actionNoteSave": "зачувај",
158 | "actionNoteCancel": "откажи",
159 | "error": "Се појави грешка... (подетално)",
160 | "denied": "Забранет пристап",
161 | "invalidpass": "Неточна лозинка",
162 | "addList": "Направи нова листа",
163 | "addListDefault": "Todo",
164 | "renameList": "Преименувај листа",
165 | "deleteList": "Со ова ке ја избишете листата и сите задачи во неа.\nПродолжи?",
166 | "clearCompleted": "This will delete all completed tasks in the list.\nAre you sure?",
167 | "settingsSaved": "Промените зачувани. Вчитувам..."
168 | }
169 |
--------------------------------------------------------------------------------
/src/includes/lang/sl.json:
--------------------------------------------------------------------------------
1 | {
2 | "_header": {
3 | "ver": "v1.3.2",
4 | "date": "2010-01-08",
5 | "language": "Slovenian",
6 | "original_name": "slovensko",
7 | "author": "Janez Troha"
8 | },
9 | "My Tiny Todolist": "Moj Seznam Nalog",
10 | "htab_newtask": "Naloga",
11 | "htab_search": "Iskalnik",
12 | "btn_add": "Dodaj",
13 | "btn_search": "Išči",
14 | "advanced_add": "Napredno",
15 | "searching": "Searching for",
16 | "tasks": "Naloge",
17 | "taskdate_inline_created": "created at %s",
18 | "taskdate_inline_completed": "Completed at %s",
19 | "taskdate_inline_duedate": "Due %s",
20 | "taskdate_created": "Datum nastanka",
21 | "taskdate_completed": "Datum zaključka",
22 | "edit_task": "Uredi nalogo",
23 | "add_task": "Nova Naloga",
24 | "priority": "Pomembnost",
25 | "task": "Naloga",
26 | "note": "Beležka",
27 | "tags": "Oznake",
28 | "save": "Shrani",
29 | "cancel": "Prekliči",
30 | "password": "Geslo",
31 | "btn_login": "Prijava",
32 | "a_login": "Prijava",
33 | "a_logout": "Odjava",
34 | "public_tasks": "Javne Naloge",
35 | "tagcloud": "Tags",
36 | "tagfilter_cancel": "ročno sortiraj",
37 | "sortByHand": "Sortiraj ročno",
38 | "sortByPriority": "Sortiraj po pomembnosti",
39 | "sortByDueDate": "Sortiraj po zapadlosti",
40 | "sortByDateCreated": "Sort by date created",
41 | "sortByDateModified": "Sort by date modified",
42 | "due": "zapadlost",
43 | "daysago": "pred %d",
44 | "indays": "čez %d dni",
45 | "months_short": [
46 | "Jan",
47 | "Feb",
48 | "Mar",
49 | "Apr",
50 | "Maj",
51 | "Jun",
52 | "Jul",
53 | "Avg",
54 | "Sep",
55 | "Okt",
56 | "Nov",
57 | "Dec"
58 | ],
59 | "months_long": [
60 | "Januar",
61 | "Februar",
62 | "Marc",
63 | "April",
64 | "Maj",
65 | "Junij",
66 | "Julij",
67 | "Avgust",
68 | "September",
69 | "Oktober",
70 | "November",
71 | "December"
72 | ],
73 | "days_min": [
74 | "Ned",
75 | "Pon",
76 | "Tor",
77 | "Sre",
78 | "Čet",
79 | "Pet",
80 | "Sob"
81 | ],
82 | "days_long": [
83 | "Nedelja",
84 | "Ponedeljek",
85 | "Torek",
86 | "Sreda",
87 | "Četrtek",
88 | "Petek",
89 | "Sobota"
90 | ],
91 | "today": "danes",
92 | "yesterday": "včeraj",
93 | "tomorrow": "jutri",
94 | "f_past": "čez rok",
95 | "f_today": "Danes in jutri",
96 | "f_soon": "Kmalu",
97 | "action_edit": "Uredi",
98 | "action_note": "Uredi Beležko",
99 | "action_delete": "Odstrani",
100 | "action_priority": "Pomembnost",
101 | "action_move": "Premakni v",
102 | "notes": "Beležke:",
103 | "notes_show": "Skrij",
104 | "notes_hide": "Prikaži",
105 | "list_new": "Nov seznam",
106 | "list_rename": "Preimenjuj seznam",
107 | "list_delete": "Odstrani seznam",
108 | "list_publish": "Objavi seznam",
109 | "list_showcompleted": "Show completed tasks",
110 | "list_clearcompleted": "Clear completed tasks",
111 | "list_select": "Select list",
112 | "list_export": "Export",
113 | "list_export_csv": "CSV",
114 | "list_export_ical": "iCalendar",
115 | "list_rssfeed": "RSS Feed",
116 | "alltags": "Vse oznake:",
117 | "alltags_show": "Prikaži vse",
118 | "alltags_hide": "Skrij vse",
119 | "a_settings": "Nastavitve",
120 | "rss_feed": "RSS Vir",
121 | "feed_title": "%s",
122 | "feed_completed_tasks": "Completed tasks",
123 | "feed_modified_tasks": "Modified tasks",
124 | "feed_new_tasks": "New tasks",
125 | "alltasks": "All tasks",
126 | "set_header": "Nastavitve",
127 | "set_title": "Naslov",
128 | "set_title_descr": "(uredi če želiš spremeniti privzeti naslov)",
129 | "set_language": "Jezik vmesnika",
130 | "set_protection": "Zaščiteno z geslom",
131 | "set_enabled": "Vključeno",
132 | "set_disabled": "Izključeno",
133 | "set_newpass": "Geslo",
134 | "set_newpass_descr": "(pusti polje prazno, če ne želiš spremeniti gesla)",
135 | "set_smartsyntax": "Pametne vnos",
136 | "set_smartsyntax_descr": "(/pomembnost/ naloga /oznake/)",
137 | "set_timezone": "Time zone",
138 | "set_autotag": "Samodejne oznake",
139 | "set_autotag_descr": "(samodejno doda oznako trenutno izbranega filtra oznake)",
140 | "set_sessions": "Shranjevanje seje",
141 | "set_sessions_php": "PHP",
142 | "set_sessions_files": "Datoteka",
143 | "set_firstdayofweek": "Prvi dan v tednu",
144 | "set_custom": "Custom",
145 | "set_date": "Format datuma",
146 | "set_date2": "Short Date format",
147 | "set_shortdate": "Kratki format datuma",
148 | "set_clock": "Format prikaza ure",
149 | "set_12hour": "12-urni",
150 | "set_24hour": "24-urni",
151 | "set_submit": "Shrani spremembe",
152 | "set_cancel": "Prekliči",
153 | "set_showdate": "Show task date in list",
154 | "confirmDelete": "Ali ste prepričani da želite izbisati izbrano nalogo?",
155 | "confirmLeave": "There can be unsaved data. Do you really want to leave?",
156 | "actionNoteSave": "shrani",
157 | "actionNoteCancel": "prekliči",
158 | "error": "Napaka (klikni za podrobnosti)",
159 | "denied": "Dostop zavrnjen",
160 | "invalidpass": "Napačno geslo",
161 | "addList": "Ustvari nov seznam",
162 | "addListDefault": "Todo",
163 | "renameList": "Preimenjuj seznam",
164 | "deleteList": "Ta ukaz bo izbrisal tudi vse naloge, v tem seznamu.\nAli ste prepričani?",
165 | "clearCompleted": "This will delete all completed tasks in the list.\nAre you sure?",
166 | "settingsSaved": "Nastavitve shranjene. Posodabljam..."
167 | }
168 |
--------------------------------------------------------------------------------
/src/includes/lang/cz.json:
--------------------------------------------------------------------------------
1 | {
2 | "_header": {
3 | "ver": "v1.3.4",
4 | "date": "2010-04-09",
5 | "language": "Czech",
6 | "original_name": "Čeština",
7 | "author": "Adam Heinrich",
8 | "author_url": "http://www.adamh.cz"
9 | },
10 | "My Tiny Todolist": "My Tiny Todolist",
11 | "htab_newtask": "Nový úkol",
12 | "htab_search": "Hledat",
13 | "btn_add": "Nový",
14 | "btn_search": "Hledat",
15 | "advanced_add": "Rozšířené",
16 | "searching": "Vyhledávání",
17 | "tasks": "Úkoly",
18 | "taskdate_inline_created": "created at %s",
19 | "taskdate_inline_completed": "Completed at %s",
20 | "taskdate_inline_duedate": "Due %s",
21 | "taskdate_created": "Datum vytvoření",
22 | "taskdate_completed": "Datum splnění",
23 | "edit_task": "Upravit úkol",
24 | "add_task": "Nový úkol",
25 | "priority": "Priorita",
26 | "task": "Úkol",
27 | "note": "Poznámka",
28 | "tags": "Tagy",
29 | "save": "Uložit",
30 | "cancel": "Zrušit",
31 | "password": "Heslo",
32 | "btn_login": "Login",
33 | "a_login": "Přihlásit",
34 | "a_logout": "Odhlásit",
35 | "public_tasks": "Veřejné úkoly",
36 | "tagcloud": "Tags",
37 | "tagfilter_cancel": "zrušit filtry",
38 | "sortByHand": "Třídit ručně",
39 | "sortByPriority": "Třídit podle priority",
40 | "sortByDueDate": "Třídit podle termínu",
41 | "sortByDateCreated": "Sort by date created",
42 | "sortByDateModified": "Sort by date modified",
43 | "due": "Termín",
44 | "daysago": "před %d dny",
45 | "indays": "ve %d dnech",
46 | "months_short": [
47 | "Led",
48 | "Úno",
49 | "Bře",
50 | "Dub",
51 | "Kvě",
52 | "Če6",
53 | "Če7",
54 | "Srp",
55 | "Zář",
56 | "Říj",
57 | "Lis",
58 | "Pro"
59 | ],
60 | "months_long": [
61 | "Leden",
62 | "Únor",
63 | "Březen",
64 | "Duben",
65 | "Květen",
66 | "Červen",
67 | "Červenec",
68 | "Srpen",
69 | "Září",
70 | "Říjen",
71 | "Listopad",
72 | "Prosinec"
73 | ],
74 | "days_min": [
75 | "Ne",
76 | "Po",
77 | "Út",
78 | "St",
79 | "Čt",
80 | "Pá",
81 | "So"
82 | ],
83 | "days_long": [
84 | "Neděle",
85 | "Pondělí",
86 | "Úterý",
87 | "Středa",
88 | "Čtvrtek",
89 | "Pátek",
90 | "Sobota"
91 | ],
92 | "today": "dnes",
93 | "yesterday": "včera",
94 | "tomorrow": "zítra",
95 | "f_past": "Overdue",
96 | "f_today": "Dnes a zítra",
97 | "f_soon": "Brzy",
98 | "action_edit": "Upravit",
99 | "action_note": "Upravit poznámku",
100 | "action_delete": "Smazat",
101 | "action_priority": "Priorita",
102 | "action_move": "Přesunout do",
103 | "notes": "Poznámky:",
104 | "notes_show": "Zobrazit",
105 | "notes_hide": "Skrýt",
106 | "list_new": "Nový seznam",
107 | "list_rename": "Přejmenovat seznam",
108 | "list_delete": "Smazat seznam",
109 | "list_publish": "Zveřejnit seznam",
110 | "list_showcompleted": "Zobrazit splněné úkoly",
111 | "list_clearcompleted": "Smazat splněné úkoly",
112 | "list_select": "Select list",
113 | "list_export": "Export",
114 | "list_export_csv": "CSV",
115 | "list_export_ical": "iCalendar",
116 | "list_rssfeed": "RSS Feed",
117 | "alltags": "Všechny tagy:",
118 | "alltags_show": "Zobrazit vše",
119 | "alltags_hide": "Skrýt vše",
120 | "a_settings": "Nastavení",
121 | "rss_feed": "RSS kanál",
122 | "feed_title": "%s",
123 | "feed_completed_tasks": "Completed tasks",
124 | "feed_modified_tasks": "Modified tasks",
125 | "feed_new_tasks": "New tasks",
126 | "alltasks": "All tasks",
127 | "set_header": "Nastavení",
128 | "set_title": "Titulek",
129 | "set_title_descr": "(zadejte, pokud chcete změnit výchozí titulek)",
130 | "set_language": "Jazyk",
131 | "set_protection": "Zaheslováno",
132 | "set_enabled": "Zapnuto",
133 | "set_disabled": "Vypnuto",
134 | "set_newpass": "Nové heslo",
135 | "set_newpass_descr": "(nevyplňujte, pokud nechcete měnit stávající heslo)",
136 | "set_smartsyntax": "\"Smart\" syntaxe",
137 | "set_smartsyntax_descr": "(Zápis: \"/priorita/ test úkolu /tagy/\")",
138 | "set_timezone": "Time zone",
139 | "set_autotag": "Automatické tagování",
140 | "set_autotag_descr": "(automaticky přiřadí k tagům text z filtru)",
141 | "set_sessions": "Správa sessions",
142 | "set_sessions_php": "PHP",
143 | "set_sessions_files": "Soubory",
144 | "set_firstdayofweek": "První den v týdnu",
145 | "set_custom": "Custom",
146 | "set_date": "Formát data",
147 | "set_date2": "Short Date format",
148 | "set_shortdate": "Zkrácený formát data",
149 | "set_clock": "Formát času",
150 | "set_12hour": "12 hodinový",
151 | "set_24hour": "24 hodinový",
152 | "set_submit": "Uložit změny",
153 | "set_cancel": "Zrušit",
154 | "set_showdate": "Zobrazit v seznamu datum vytvoření úkolu",
155 | "confirmDelete": "Opravdu chcete smazat úkol?",
156 | "confirmLeave": "There can be unsaved data. Do you really want to leave?",
157 | "actionNoteSave": "uložit",
158 | "actionNoteCancel": "zrušit",
159 | "error": "Objevil se problém (klikněte pro více informací)",
160 | "denied": "Přístup odepřen",
161 | "invalidpass": "Špatné heslo",
162 | "addList": "Vytvořit nový seznam",
163 | "addListDefault": "Todo",
164 | "renameList": "Přejmenovat seznam",
165 | "deleteList": "Tímto smažete seznam a všechny úkoly v něm.\nChcete pokračovat?",
166 | "clearCompleted": "Tímto smažete všechny splněné úkoly.\nChcete pokračovat?",
167 | "settingsSaved": "Nastavení uloženo. Načítám..."
168 | }
169 |
--------------------------------------------------------------------------------
/src/includes/lang/da.json:
--------------------------------------------------------------------------------
1 | {
2 | "_header": {
3 | "ver": "v1.3.5",
4 | "date": "2010-05-22",
5 | "language": "Danish",
6 | "original_name": "Dansk",
7 | "author": "Per Jensen",
8 | "author_url": "http://www.plads9000.dk"
9 | },
10 | "My Tiny Todolist": "My Tiny Todolist",
11 | "htab_newtask": "Ny opgave",
12 | "htab_search": "Søg",
13 | "btn_add": "Tilføj",
14 | "btn_search": "Søg",
15 | "advanced_add": "Udvidet",
16 | "searching": "Søger efter",
17 | "tasks": "Opgaver",
18 | "taskdate_inline_created": "created at %s",
19 | "taskdate_inline_completed": "Completed at %s",
20 | "taskdate_inline_duedate": "Due %s",
21 | "taskdate_created": "Dato for oprettelse",
22 | "taskdate_completed": "Dato for færdiggørelse",
23 | "edit_task": "Rediger opgave",
24 | "add_task": "Ny opgave",
25 | "priority": "Prioritet",
26 | "task": "Opgave",
27 | "note": "Note",
28 | "tags": "Tags",
29 | "save": "Gem",
30 | "cancel": "Annuller",
31 | "password": "Password",
32 | "btn_login": "Login",
33 | "a_login": "Login",
34 | "a_logout": "Logout",
35 | "public_tasks": "Offentlige opgaver",
36 | "tagcloud": "Tags",
37 | "tagfilter_cancel": "Annuller filter",
38 | "sortByHand": "Sorter manuelt",
39 | "sortByPriority": "Sorter efter prioritet",
40 | "sortByDueDate": "Sorter efter forfaldsdato",
41 | "sortByDateCreated": "Sort by date created",
42 | "sortByDateModified": "Sort by date modified",
43 | "due": "Forfald",
44 | "daysago": "%d dage siden",
45 | "indays": "om %d dage",
46 | "months_short": [
47 | "Jan",
48 | "Feb",
49 | "Mar",
50 | "Apr",
51 | "Maj",
52 | "Jun",
53 | "Jul",
54 | "Aug",
55 | "Sep",
56 | "Okt",
57 | "Nov",
58 | "Dec"
59 | ],
60 | "months_long": [
61 | "Januar",
62 | "Februar",
63 | "Marts",
64 | "April",
65 | "Maj",
66 | "Juni",
67 | "Juli",
68 | "August",
69 | "September",
70 | "Oktober",
71 | "November",
72 | "December"
73 | ],
74 | "days_min": [
75 | "Sø",
76 | "Ma",
77 | "Ti",
78 | "On",
79 | "To",
80 | "Fr",
81 | "Lø"
82 | ],
83 | "days_long": [
84 | "Søndag",
85 | "Mandag",
86 | "Tirsdag",
87 | "Onsdag",
88 | "Torsdag",
89 | "Fredag",
90 | "Lørdag"
91 | ],
92 | "today": "I dag",
93 | "yesterday": "I går",
94 | "tomorrow": "I morgen",
95 | "f_past": "Forfalden",
96 | "f_today": "I dag og i morgen",
97 | "f_soon": "Snart",
98 | "action_edit": "Rediger",
99 | "action_note": "Rediger note",
100 | "action_delete": "Slet",
101 | "action_priority": "Prioritet",
102 | "action_move": "Flyt til",
103 | "notes": "Noter:",
104 | "notes_show": "Vis",
105 | "notes_hide": "Gem",
106 | "list_new": "Ny liste",
107 | "list_rename": "Omdøb liste",
108 | "list_delete": "Slet liste",
109 | "list_publish": "Udgiv liste",
110 | "list_showcompleted": "Vis fuldførte opgaver",
111 | "list_clearcompleted": "Slet fuldførte opgaver",
112 | "list_select": "Select list",
113 | "list_export": "Export",
114 | "list_export_csv": "CSV",
115 | "list_export_ical": "iCalendar",
116 | "list_rssfeed": "RSS Feed",
117 | "alltags": "Alle tags:",
118 | "alltags_show": "Vis alle",
119 | "alltags_hide": "Gem alle",
120 | "a_settings": "Indstillinger",
121 | "rss_feed": "RSS Feed",
122 | "feed_title": "%s",
123 | "feed_completed_tasks": "Completed tasks",
124 | "feed_modified_tasks": "Modified tasks",
125 | "feed_new_tasks": "New tasks",
126 | "alltasks": "All tasks",
127 | "set_header": "Indstillinger",
128 | "set_title": "Titel",
129 | "set_title_descr": "(angiv hvis du ønsker at ændre standard titel)",
130 | "set_language": "Sprog",
131 | "set_protection": "Password beskyttelse",
132 | "set_enabled": "Aktiveret",
133 | "set_disabled": "Deaktiveret",
134 | "set_newpass": "Nyt password",
135 | "set_newpass_descr": "(lad være tomt hvis aktuelt password ikke skal ændres)",
136 | "set_smartsyntax": "Smart syntaks",
137 | "set_smartsyntax_descr": "(/ prioritet / opgave / tags /)",
138 | "set_timezone": "Time zone",
139 | "set_autotag": "Automatisk tagging",
140 | "set_autotag_descr": "(Tilføjer automatisk tags til nyoprettede opgaver)",
141 | "set_sessions": "Sessions håndtering",
142 | "set_sessions_php": "PHP",
143 | "set_sessions_files": "Filer",
144 | "set_firstdayofweek": "Første ugedag",
145 | "set_custom": "Custom",
146 | "set_date": "Dato format",
147 | "set_date2": "Short Date format",
148 | "set_shortdate": "Kort datoformat",
149 | "set_clock": "Tidsformat",
150 | "set_12hour": "12-timer",
151 | "set_24hour": "24-timer",
152 | "set_submit": "Gem ændringer",
153 | "set_cancel": "Annuller",
154 | "set_showdate": "Vis opgavedato i listen",
155 | "confirmDelete": "Er du sikker på at du vil slette denne opgave?",
156 | "confirmLeave": "There can be unsaved data. Do you really want to leave?",
157 | "actionNoteSave": "gem",
158 | "actionNoteCancel": "annuller",
159 | "error": "Der opstod en fejl (klik for detaljer)",
160 | "denied": "Adgang nægtet",
161 | "invalidpass": "Forkert password",
162 | "addList": "Opret ny liste",
163 | "addListDefault": "Todo",
164 | "renameList": "Omdøb liste",
165 | "deleteList": "Dette vil slette den aktuelle liste og alle opgaver i den.\nEr du sikker?",
166 | "clearCompleted": "Dette vil slette alle afsluttede opgaver i listen.\nEr du sikker?",
167 | "settingsSaved": "Indstillinger gemt. Genindlæser..."
168 | }
169 |
--------------------------------------------------------------------------------
/src/includes/lang/it.json:
--------------------------------------------------------------------------------
1 | {
2 | "_header": {
3 | "ver": "v1.3.2",
4 | "date": "2010-01-24",
5 | "language": "Italian",
6 | "original_name": "Italiano",
7 | "author": "Giuseppe Dessì"
8 | },
9 | "My Tiny Todolist": "Tasks",
10 | "htab_newtask": "Nuovo Task",
11 | "htab_search": "Cerca",
12 | "btn_add": "Aggiungi",
13 | "btn_search": "Cerca",
14 | "advanced_add": "Avanzato",
15 | "searching": "Cercando",
16 | "tasks": "Tasks",
17 | "taskdate_inline_created": "created at %s",
18 | "taskdate_inline_completed": "Completed at %s",
19 | "taskdate_inline_duedate": "Due %s",
20 | "taskdate_created": "Data di creazione",
21 | "taskdate_completed": "Data di scadenza",
22 | "edit_task": "Modifica Task",
23 | "add_task": "Nuovo Task",
24 | "priority": "Priorità",
25 | "task": "Task",
26 | "note": "Nota",
27 | "tags": "Tags",
28 | "save": "Salva",
29 | "cancel": "Annulla",
30 | "password": "Password",
31 | "btn_login": "Login",
32 | "a_login": "Login",
33 | "a_logout": "Logout",
34 | "public_tasks": "Tasks Pubblici",
35 | "tagcloud": "Tags",
36 | "tagfilter_cancel": "annulla filtro",
37 | "sortByHand": "Ordina",
38 | "sortByPriority": "Ordina per priorità",
39 | "sortByDueDate": "Ordina per scadenza",
40 | "sortByDateCreated": "Sort by date created",
41 | "sortByDateModified": "Sort by date modified",
42 | "due": "Scadenza",
43 | "daysago": "%d giorni fa",
44 | "indays": "entro %d giorni",
45 | "months_short": [
46 | "Gen",
47 | "Feb",
48 | "Mar",
49 | "Apr",
50 | "Mag",
51 | "Giu",
52 | "Lug",
53 | "Ago",
54 | "Set",
55 | "Ott",
56 | "Nov",
57 | "Dic"
58 | ],
59 | "months_long": [
60 | "Gennaio",
61 | "Febbraio",
62 | "Marzo",
63 | "Aprile",
64 | "Maggio",
65 | "Giugno",
66 | "Luglio",
67 | "Agosto",
68 | "Settembre",
69 | "Ottobre",
70 | "Novembre",
71 | "Dicembre"
72 | ],
73 | "days_min": [
74 | "Do",
75 | "Lu",
76 | "Ma",
77 | "Me",
78 | "Gi",
79 | "Ve",
80 | "Sa"
81 | ],
82 | "days_long": [
83 | "Domenica",
84 | "Lunedì",
85 | "martedì",
86 | "Mercoledì",
87 | "Giovedì",
88 | "Venerdì",
89 | "Sabato"
90 | ],
91 | "today": "oggi",
92 | "yesterday": "ieri",
93 | "tomorrow": "domani",
94 | "f_past": "Scaduto",
95 | "f_today": "Oggi e domani",
96 | "f_soon": "Prossimamente",
97 | "action_edit": "Modifica",
98 | "action_note": "Modifica Nota",
99 | "action_delete": "Elimina",
100 | "action_priority": "Priorità",
101 | "action_move": "sposta in",
102 | "notes": "Note:",
103 | "notes_show": "Mostra",
104 | "notes_hide": "Nascondi",
105 | "list_new": "Nuova lista",
106 | "list_rename": "Rinomina lista",
107 | "list_delete": "Elimina lista",
108 | "list_publish": "Pubblica lista",
109 | "list_showcompleted": "Show completed tasks",
110 | "list_clearcompleted": "Clear completed tasks",
111 | "list_select": "Select list",
112 | "list_export": "Export",
113 | "list_export_csv": "CSV",
114 | "list_export_ical": "iCalendar",
115 | "list_rssfeed": "RSS Feed",
116 | "alltags": "Tutti i tags:",
117 | "alltags_show": "Visualizza tutti",
118 | "alltags_hide": "Nacondi tutti",
119 | "a_settings": "Impostazioni",
120 | "rss_feed": "RSS Feed",
121 | "feed_title": "%s",
122 | "feed_completed_tasks": "Completed tasks",
123 | "feed_modified_tasks": "Modified tasks",
124 | "feed_new_tasks": "New tasks",
125 | "alltasks": "All tasks",
126 | "set_header": "Impostazioni",
127 | "set_title": "Titolo",
128 | "set_title_descr": "(specifica se vuoi cambiare il titolo predefinito)",
129 | "set_language": "Linguaggio",
130 | "set_protection": "Protezione con password",
131 | "set_enabled": "Attivo",
132 | "set_disabled": "Disattivo",
133 | "set_newpass": "Nuova password",
134 | "set_newpass_descr": "(non compilare se non vuoi cambiare password)",
135 | "set_smartsyntax": "Smart syntax",
136 | "set_smartsyntax_descr": "(/priority/ task /tags/)",
137 | "set_timezone": "Time zone",
138 | "set_autotag": "Tagging automatico",
139 | "set_autotag_descr": "(aggiunge automaticamente i tag per i nuovi compiti)",
140 | "set_sessions": "Meccanismo di gestione sessione",
141 | "set_sessions_php": "PHP",
142 | "set_sessions_files": "Files",
143 | "set_firstdayofweek": "Primo giorno della settimana",
144 | "set_custom": "Custom",
145 | "set_date": "Formato data",
146 | "set_date2": "Short Date format",
147 | "set_shortdate": "Formato data abbreviata",
148 | "set_clock": "Formato orario",
149 | "set_12hour": "12-hour",
150 | "set_24hour": "24-hour",
151 | "set_submit": "applica le modifiche",
152 | "set_cancel": "Annulla",
153 | "set_showdate": "Show task date in list",
154 | "confirmDelete": "Sei sicuro di voler cancellare il task?",
155 | "confirmLeave": "There can be unsaved data. Do you really want to leave?",
156 | "actionNoteSave": "salva",
157 | "actionNoteCancel": "annulla",
158 | "error": "Ci sono errori (clicca per dettagli)",
159 | "denied": "Accesso non consentito",
160 | "invalidpass": "Password errata",
161 | "addList": "Crea una nuova lista",
162 | "addListDefault": "Todo",
163 | "renameList": "Rinomina lista list",
164 | "deleteList": "Questo eliminerà la lista corrente e tutti i task inclusi.\nSei sicuro?",
165 | "clearCompleted": "This will delete all completed tasks in the list.\nAre you sure?",
166 | "settingsSaved": "Impostazioni salvate. Ricaricando..."
167 | }
168 |
--------------------------------------------------------------------------------
/src/includes/lang/ar.json:
--------------------------------------------------------------------------------
1 | {
2 | "_header": {
3 | "ver": "v1.4.2",
4 | "date": "2011-04-04",
5 | "language": "Arabic",
6 | "original_name": "عربي",
7 | "author": "Majid Al-Dharrab",
8 | "author_email": "majid@aldharrab.com",
9 | "rtl": 1
10 | },
11 | "My Tiny Todolist": "My Tiny Todolist",
12 | "htab_newtask": "مهمة جديدة",
13 | "htab_search": "بحث",
14 | "btn_add": "أضِف",
15 | "btn_search": "ابحث",
16 | "advanced_add": "متقدم",
17 | "searching": "يبحث عن",
18 | "tasks": "المهمات",
19 | "taskdate_inline_created": "أنشئت في %s",
20 | "taskdate_inline_completed": "اكتملت في %s",
21 | "taskdate_inline_duedate": "تنتهي في %s",
22 | "taskdate_created": "أنشئت",
23 | "taskdate_completed": "اكتملت",
24 | "edit_task": "حرِّر المهمة",
25 | "add_task": "مهمة جديدة",
26 | "priority": "الأولوية",
27 | "task": "المهمة",
28 | "note": "الملاحظة",
29 | "tags": "الوسوم",
30 | "save": "احفظ",
31 | "cancel": "ألغِ",
32 | "password": "كلمة السر",
33 | "btn_login": "لُج",
34 | "a_login": "لُج",
35 | "a_logout": "اخرج",
36 | "public_tasks": "المهمات العامة",
37 | "tagcloud": "الوسوم",
38 | "tagfilter_cancel": "ألغِ المرشح",
39 | "sortByHand": "رتِّب يدويًا",
40 | "sortByPriority": "رتِّب حسب الأولوية",
41 | "sortByDueDate": "رتِّب حسب تاريخ الانتهاء",
42 | "sortByDateCreated": "رتِّب حسب تاريخ الإنشاء",
43 | "sortByDateModified": "رتِّب حسب تاريخ التعديل",
44 | "due": "تنتهي",
45 | "daysago": "منذ %d أيام",
46 | "indays": "خلال %d أيام",
47 | "months_short": [
48 | "ينا",
49 | "فبر",
50 | "مار",
51 | "أبر",
52 | "ماي",
53 | "يون",
54 | "يول",
55 | "أغس",
56 | "سبت",
57 | "أكت",
58 | "نوف",
59 | "ديس"
60 | ],
61 | "months_long": [
62 | "يناير",
63 | "فبراير",
64 | "مارس",
65 | "أبريل",
66 | "مايو",
67 | "يونيو",
68 | "يوليو",
69 | "أغسطس",
70 | "سبتمبر",
71 | "أكتوبر",
72 | "نوفمبر",
73 | "ديسمبر"
74 | ],
75 | "days_min": [
76 | "أحد",
77 | "إثنين",
78 | "ثلاثاء",
79 | "أربعاء",
80 | "خميس",
81 | "جمعة",
82 | "سبت"
83 | ],
84 | "days_long": [
85 | "الأحد",
86 | "الإثنين",
87 | "الثلاثاء",
88 | "الأربعاء",
89 | "الخميس",
90 | "الجمعة",
91 | "السبت"
92 | ],
93 | "today": "اليوم",
94 | "yesterday": "أمس",
95 | "tomorrow": "غدًا",
96 | "f_past": "متأخر",
97 | "f_today": "اليوم وغدًا",
98 | "f_soon": "قريبًا",
99 | "action_edit": "حرِّر",
100 | "action_note": "حرِّر الملاحظة",
101 | "action_delete": "احذف",
102 | "action_priority": "الأولوية",
103 | "action_move": "انقل إلى",
104 | "notes": "الملاحظات:",
105 | "notes_show": "أظهر",
106 | "notes_hide": "أخفِ",
107 | "list_new": "قائمة جديدة",
108 | "list_rename": "غيِّر اسم القائمة",
109 | "list_delete": "احذف القائمة",
110 | "list_publish": "انشر القائمة",
111 | "list_showcompleted": "أظهر المهمات المكتملة",
112 | "list_clearcompleted": "امحُ المهمات المكتملة",
113 | "list_select": "اختر القائمة",
114 | "list_export": "صدِّر",
115 | "list_export_csv": "CSV",
116 | "list_export_ical": "iCalendar",
117 | "list_rssfeed": "تلقيم آرإسإس",
118 | "alltags": "كل الوسوم:",
119 | "alltags_show": "أظهر الكل",
120 | "alltags_hide": "أخفِ الكل",
121 | "a_settings": "الإعدادات",
122 | "rss_feed": "تلقيم آرإسإس",
123 | "feed_title": "%s",
124 | "feed_completed_tasks": "المهمات المكتملة",
125 | "feed_modified_tasks": "المهمات المعدلة",
126 | "feed_new_tasks": "المهمات الجديدة",
127 | "alltasks": "كل المهمات",
128 | "set_header": "الإعدادات",
129 | "set_title": "العنوان",
130 | "set_title_descr": "(حدِّد ما إذا أردت تغيير العنوان المبدئي)",
131 | "set_language": "اللغة",
132 | "set_protection": "الحماية بكلمة سر",
133 | "set_enabled": "مفعلة",
134 | "set_disabled": "معطلة",
135 | "set_newpass": "كلمة السر الجديدة",
136 | "set_newpass_descr": "(اتركها فارغة إذا لم تكن ترغب بتغيير كلمة السر الحالية)",
137 | "set_smartsyntax": "الصياغة الذكية",
138 | "set_smartsyntax_descr": "(/الأولوية/ المهمة /الوسوم/)",
139 | "set_timezone": "المنطقة الزمنية",
140 | "set_autotag": "الوسوم الآلية",
141 | "set_autotag_descr": "(يضيف الوسوم الموجودة في مرشّح الوسوم إلى المهمات الجديدة)",
142 | "set_sessions": "طريقة التعامل مع الجلسات",
143 | "set_sessions_php": "بيإتشبي",
144 | "set_sessions_files": "ملفات",
145 | "set_firstdayofweek": "أول أيام الأسبوع",
146 | "set_custom": "مخصصة",
147 | "set_date": "صيغة التواريخ",
148 | "set_date2": "الصيغة القصيرة للتواريخ",
149 | "set_shortdate": "الصيغة القصيرة لتواريخ السنة الجارية",
150 | "set_clock": "صيغة الوقت",
151 | "set_12hour": "12 ساعة",
152 | "set_24hour": "24 ساعة",
153 | "set_submit": "أرسل التغييرات",
154 | "set_cancel": "ألغِ",
155 | "set_showdate": "تواريخ المهمات تظهر في القائمة",
156 | "confirmDelete": "هل أنت متأكد أنك تود حذف المهمة؟",
157 | "confirmLeave": "قد تود بيانات لم تُحفظ. هل تود المغادرة حقًا؟",
158 | "actionNoteSave": "احفظ",
159 | "actionNoteCancel": "ألغِ",
160 | "error": "حدث خطأ ما (انقر لمشاهدة التفاصيل)",
161 | "denied": "لم يُسمح بالنفاذ",
162 | "invalidpass": "كلمة السر خاطئة",
163 | "addList": "أنشئ قائمة جديدة",
164 | "addListDefault": "Todo",
165 | "renameList": "غيِّر اسم القائمة",
166 | "deleteList": "ستُحذف القائمة الحالية وكل ما بها من مهمات.\nهل أنت متأكد أنك تود فعل ذلك؟",
167 | "clearCompleted": "ستُحذف كل المهمات المكتملة في القائمة.\nهل أنت متأكد أنك تود فعل ذلك؟",
168 | "settingsSaved": "حُفظت الإعدادات. يعيد التحميل..."
169 | }
170 |
--------------------------------------------------------------------------------
/src/includes/lang/sk.json:
--------------------------------------------------------------------------------
1 | {
2 | "_header": {
3 | "ver": "v1.3.6",
4 | "date": "2010-12-16",
5 | "language": "Slovak",
6 | "original_name": "Slovenčina",
7 | "author": "Ľubomír Molent",
8 | "author_url": ""
9 | },
10 | "My Tiny Todolist": "My Tiny Todolist",
11 | "htab_newtask": "Nová úloha",
12 | "htab_search": "Hľadať",
13 | "btn_add": "Pridať",
14 | "btn_search": "Hľadať",
15 | "advanced_add": "Rozšírené",
16 | "searching": "Vyhľadávanie",
17 | "tasks": "Úlohy",
18 | "taskdate_inline_created": "created at %s",
19 | "taskdate_inline_completed": "Completed at %s",
20 | "taskdate_inline_duedate": "Due %s",
21 | "taskdate_created": "Dátum vytvorenia",
22 | "taskdate_completed": "Dátum splnenia",
23 | "edit_task": "Upraviť úlohu",
24 | "add_task": "Nová úloha",
25 | "priority": "Priorita",
26 | "task": "Úloha",
27 | "note": "Poznámka",
28 | "tags": "Tagy",
29 | "save": "Uložit",
30 | "cancel": "Zrušit",
31 | "password": "Heslo",
32 | "btn_login": "Login",
33 | "a_login": "Prihlásiť",
34 | "a_logout": "Odhlásiť",
35 | "public_tasks": "Verejné úlohy",
36 | "tagcloud": "Tags",
37 | "tagfilter_cancel": "zrušit filtre",
38 | "sortByHand": "Triediť ručne",
39 | "sortByPriority": "Triediť podľa priority",
40 | "sortByDueDate": "Triediť podľa termínu",
41 | "sortByDateCreated": "Sort by date created",
42 | "sortByDateModified": "Sort by date modified",
43 | "due": "Termín",
44 | "daysago": "pred %d dňami",
45 | "indays": "o %d dní",
46 | "months_short": [
47 | "Jan",
48 | "Feb",
49 | "Mar",
50 | "Apr",
51 | "Maj",
52 | "Jun",
53 | "Jul",
54 | "Aug",
55 | "Sep",
56 | "Okt",
57 | "Nov",
58 | "Dec"
59 | ],
60 | "months_long": [
61 | "Január",
62 | "Február",
63 | "Marec",
64 | "Apríl",
65 | "Máj",
66 | "Jún",
67 | "Júl",
68 | "August",
69 | "September",
70 | "Október",
71 | "November",
72 | "December"
73 | ],
74 | "days_min": [
75 | "Ne",
76 | "Po",
77 | "Ut",
78 | "St",
79 | "Št",
80 | "Pi",
81 | "So"
82 | ],
83 | "days_long": [
84 | "Nedľa",
85 | "Pondelok",
86 | "Utorok",
87 | "Streda",
88 | "Štvrtok",
89 | "Piatok",
90 | "Sobota"
91 | ],
92 | "today": "dnes",
93 | "yesterday": "včera",
94 | "tomorrow": "zajtra",
95 | "f_past": "Overdue",
96 | "f_today": "Dnes a zajtra",
97 | "f_soon": "Čoskoro",
98 | "action_edit": "Upraviť",
99 | "action_note": "Upraviť poznámku",
100 | "action_delete": "Zmazať",
101 | "action_priority": "Priorita",
102 | "action_move": "Presunúť do",
103 | "notes": "Poznámky:",
104 | "notes_show": "Zobraziť",
105 | "notes_hide": "Skryť",
106 | "list_new": "Nový zoznam",
107 | "list_rename": "Premenovať zoznam",
108 | "list_delete": "Zmazať zoznam",
109 | "list_publish": "Zverejniť zoznam",
110 | "list_showcompleted": "Zobraziť splnené úlohy",
111 | "list_clearcompleted": "Zmazať splnené úlohy",
112 | "list_select": "Select list",
113 | "list_export": "Export",
114 | "list_export_csv": "CSV",
115 | "list_export_ical": "iCalendar",
116 | "list_rssfeed": "RSS Feed",
117 | "alltags": "Všetky tagy:",
118 | "alltags_show": "Zobraziť všetko",
119 | "alltags_hide": "Skryť všetko",
120 | "a_settings": "Nastavenie",
121 | "rss_feed": "RSS kanál",
122 | "feed_title": "%s",
123 | "feed_completed_tasks": "Completed tasks",
124 | "feed_modified_tasks": "Modified tasks",
125 | "feed_new_tasks": "New tasks",
126 | "alltasks": "All tasks",
127 | "set_header": "Nastavenie",
128 | "set_title": "Titulok",
129 | "set_title_descr": "(zadajte, pokiaľ chcete zmeniť východzí titulok)",
130 | "set_language": "Jazyk",
131 | "set_protection": "Zaheslovanie",
132 | "set_enabled": "Zapnuté",
133 | "set_disabled": "Vypnuté",
134 | "set_newpass": "Nové heslo",
135 | "set_newpass_descr": "(nevypĺňajte, pokiaľ nechcete meniť nastavené heslo)",
136 | "set_smartsyntax": "\"Smart\" syntax",
137 | "set_smartsyntax_descr": "(Zápis: \"/priorita/ test úlohy /tagy/\")",
138 | "set_timezone": "Time zone",
139 | "set_autotag": "Automatické tagovanie",
140 | "set_autotag_descr": "(automaticky priradí k tagom text z filtra)",
141 | "set_sessions": "Správa sessions",
142 | "set_sessions_php": "PHP",
143 | "set_sessions_files": "Súbory",
144 | "set_firstdayofweek": "Prvný deň v týždni",
145 | "set_custom": "Custom",
146 | "set_date": "Formát dátumu",
147 | "set_date2": "Short Date format",
148 | "set_shortdate": "Zkrátený formát dátumu",
149 | "set_clock": "Formát času",
150 | "set_12hour": "12 hodinový",
151 | "set_24hour": "24 hodinový",
152 | "set_submit": "Uložiť zmeny",
153 | "set_cancel": "Zrušit",
154 | "set_showdate": "Zobrazit v zozname dátum vytvorenia úlohy",
155 | "confirmDelete": "Naozaj chcete vymazať úlohu?",
156 | "confirmLeave": "There can be unsaved data. Do you really want to leave?",
157 | "actionNoteSave": "uložit",
158 | "actionNoteCancel": "zrušit",
159 | "error": "Vyskytol sa problém (kliknite pre viac informácií)",
160 | "denied": "Prístup zamietnutý",
161 | "invalidpass": "Nesprávne heslo",
162 | "addList": "Vytvoriť nový zoznam",
163 | "addListDefault": "Todo",
164 | "renameList": "Premenovat zoznam",
165 | "deleteList": "Týmto vymažete zoznam a všetky úlohy v ňom.\nChcete pokračovat?",
166 | "clearCompleted": "Týmto vymažete všetky splnené úlohy.\nChcete pokračovat?",
167 | "settingsSaved": "Nastavenie uložené. Načítavam..."
168 | }
169 |
--------------------------------------------------------------------------------
/src/includes/class.db.mysqli.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | // ---------------------------------------------------------------------------- //
10 | class DatabaseResult_Mysqli extends DatabaseResult_Abstract
11 | {
12 | /** @var mysqli_result */
13 | protected $q;
14 |
15 | function __construct(mysqli $dbh, string $query, bool $resultless = false)
16 | {
17 | $this->q = $dbh->query($query); //throws mysqli_sql_exception
18 | }
19 |
20 | function fetchRow(): ?array
21 | {
22 | $res = $this->q->fetch_row();
23 | if ($res === null || $res === false || !is_array($res)) {
24 | return null;
25 | }
26 | return $res;
27 | }
28 |
29 | function fetchAssoc(): ?array
30 | {
31 | $res = $this->q->fetch_assoc();
32 | if ($res === null || $res === false || !is_array($res)) {
33 | return null;
34 | }
35 | return $res;
36 | }
37 | }
38 |
39 | // ---------------------------------------------------------------------------- //
40 | class Database_Mysqli extends Database_Abstract
41 | {
42 | const DBTYPE = 'mysql';
43 |
44 | /** @var mysqli */
45 | protected $dbh;
46 | protected $dbname;
47 |
48 | function __construct()
49 | {
50 | // enable throwing exceptions
51 | mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
52 | }
53 |
54 | function connect(array $params): void
55 | {
56 | $host = $params['host'];
57 | $user = $params['user'];
58 | $pass = $params['password'];
59 | $db = $params['db'];
60 | $this->dbname = $db;
61 | $this->dbh = new mysqli($host, $user, $pass, $db); //throws mysqli_sql_exception
62 | }
63 |
64 | function lastInsertId(?string $name = null): ?string
65 | {
66 | return (string) $this->dbh->insert_id;
67 | }
68 |
69 | function sq(string $query, ?array $values = null)
70 | {
71 | $q = $this->_dq($query, $values);
72 |
73 | $res = $q->fetchRow();
74 | if ($res === false || !is_array($res)) {
75 | return null;
76 | }
77 |
78 | if (sizeof($res) > 1) return $res;
79 | else return $res[0];
80 | }
81 |
82 | /*
83 | Returns single row of SELECT query as dictionary array (fetch_assoc()).
84 | */
85 | function sqa(string $query, ?array $values = null): ?array
86 | {
87 | $q = $this->_dq($query, $values);
88 | $res = $q->fetchAssoc();
89 | if ($res === false || !is_array($res)){
90 | return null;
91 | }
92 | return $res;
93 | }
94 |
95 | function dq(string $query, ?array $values = null) : DatabaseResult_Abstract
96 | {
97 | return $this->_dq($query, $values);
98 | }
99 |
100 | /*
101 | for resultless queries like INSERT,UPDATE,DELETE
102 | */
103 | function ex(string $query, ?array $values = null): void
104 | {
105 | $this->_dq($query, $values, true);
106 | }
107 |
108 | private function _dq(string $query, ?array $values = null, bool $resultless = false) : DatabaseResult_Abstract
109 | {
110 | if (null !== $values && sizeof($values) > 0)
111 | {
112 | $m = explode('?', $query);
113 | if (sizeof($m) < sizeof($values)+1) {
114 | throw new Exception("params to set MORE than query params");
115 | }
116 | if (sizeof($m) > sizeof($values)+1) {
117 | throw new Exception("params to set LESS than query params");
118 | }
119 | $query = "";
120 | for ($i=0; $i < sizeof($m)-1; $i++) {
121 | $query .= $m[$i]. $this->quote($values[$i]);
122 | }
123 | $query .= $m[$i];
124 | }
125 | $this->setLastQuery($query);
126 | return new DatabaseResult_Mysqli($this->dbh, $query, $resultless);
127 | }
128 |
129 | function affected(): int
130 | {
131 | return max( (int)$this->dbh->affected_rows, 0 );
132 | }
133 |
134 | function quote($value): string
135 | {
136 | if (null === $value) {
137 | return 'null';
138 | }
139 | return '\''. addslashes( (string) $value). '\'';
140 | }
141 |
142 | function quoteForLike(string $format, string $string): string
143 | {
144 | $string = str_replace(array('%','_'), array('\%','\_'), addslashes($string));
145 | return '\''. sprintf($format, $string). '\'';
146 | }
147 |
148 | function like(string $column, string $format, string $string): string
149 | {
150 | $column = str_replace('`', '``', $column);
151 | return '`'. $column. '` LIKE '. $this->quoteForLike($format, $string);
152 | }
153 |
154 | function ciEquals(string $column, string $value): string
155 | {
156 | $column = str_replace('`', '``', $column);
157 | return 'LOWER(`'. $column. '`) = LOWER('. $this->quote($value). ')';
158 | }
159 |
160 | function tableExists(string $table): bool
161 | {
162 | $r = $this->sq("SELECT 1 FROM information_schema.tables WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?",
163 | array($this->dbname, $table) );
164 | if ($r === false || $r === null) return false;
165 | return true;
166 | }
167 |
168 | function tableFieldExists(string $table, string $field): bool
169 | {
170 | $table = str_replace('`', '\\`', addslashes($table));
171 | $q = $this->dq("DESCRIBE `$table`");
172 | while ($r = $q->fetchRow()) {
173 | if ($r[0] == $field) return true;
174 | }
175 | return false;
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/src/feed.php:
--------------------------------------------------------------------------------
1 |
6 | Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
7 | */
8 |
9 | $dontStartSession = 1;
10 | require_once('./init.php');
11 | require_once(MTTINC. 'markup.php');
12 |
13 | $lang = Lang::instance();
14 |
15 | $listId = (int)_get('list');
16 | $db = DBConnection::instance();
17 | $listData = $db->sqa("SELECT * FROM {$db->prefix}lists WHERE id=$listId");
18 | if ( $listData && need_auth() && !$listData['published'] ) {
19 | $extra = json_decode($listData['extra'] ?? '', true, 10, JSON_INVALID_UTF8_SUBSTITUTE);
20 | $feedKey = (string) ($extra['feedKey'] ?? '');
21 | $inFeedKey = trim(_get('key'));
22 | if ($feedKey == '' || $feedKey != $inFeedKey) {
23 | die("Access denied!
List is not published.");
24 | }
25 | }
26 | if (!$listData) {
27 | die("No list found.");
28 | }
29 |
30 | $data = array();
31 | $feedType = _get('feed');
32 |
33 | if($feedType == 'completed') {
34 | $listData['_feed_descr'] = $lang->get('feed_completed_tasks');
35 | fillData( $data, $listId, 'd_completed', 'compl=1' );
36 | }
37 | elseif($feedType == 'modified') {
38 | $listData['_feed_descr'] = $lang->get('feed_modified_tasks');
39 | fillData( $data, $listId, 'd_edited', '' );
40 | }
41 | elseif($feedType == 'current') {
42 | $listData['_feed_descr'] = $lang->get('feed_new_tasks');
43 | fillData( $data, $listId, 'd_created', 'compl=0' );
44 | }
45 | elseif($feedType == 'status') {
46 | $listData['_feed_descr'] = $lang->get('feed_tasks');
47 | fillData( $data, $listId, 'd_created', '' );
48 | fillData( $data, $listId, 'd_edited', 'compl=0 AND d_edited > d_created' );
49 | fillData( $data, $listId, 'd_completed', 'compl=1' );
50 | }
51 | else {
52 | $listData['_feed_descr'] = $lang->get('feed_new_tasks');
53 | $feedType = 'tasks';
54 | fillData( $data, $listId, 'd_created', '' );
55 | }
56 |
57 | $listData['_feed_title'] = sprintf($lang->get('feed_title'), $listData['name']) . ' - '. $listData['_feed_descr'];
58 | $listData['_feed_link'] = get_mttinfo('mtt_url'). "feed.php?list=". (int)$listData['id'] . ($feedType != '' ? "&feed=". $feedType : '');
59 | $listData['_feed_type'] = $feedType;
60 | htmlarray_ref($listData);
61 |
62 | printRss($data, $listData);
63 |
64 |
65 | function fillData(array &$data, int $listId, string $field, string $sqlWhere )
66 | {
67 | $tasks = DBCore::default()->getTasksByListId($listId, $sqlWhere, "$field DESC", 100);
68 | $lang = Lang::instance();
69 | foreach ($tasks as $r)
70 | {
71 | if ($r['prio'] > 0) {
72 | $r['prio'] = '+'.$r['prio'];
73 | }
74 | $a = array(); //for _descr
75 | $a[] = $lang->get('task'). ": ". $r['title'];
76 | if ($r['prio']) {
77 | $a[] = $lang->get('priority'). ": $r[prio]";
78 | }
79 | if ($r['duedate'] != '') {
80 | $ad = explode('-', $r['duedate']);
81 | $a[] = $lang->get('due'). ": ".formatDate3(Config::get('dateformat'), (int)$ad[0], (int)$ad[1], (int)$ad[2], $lang);
82 | }
83 | if ($r['tags'] != '') {
84 | $a[] = $lang->get('tags'). ": ". str_replace(',', ', ', $r['tags']);
85 | }
86 | if ($r['compl']) {
87 | $a[] = $lang->get('taskdate_completed'). ": ". timestampToDatetime($r['d_completed']);
88 | }
89 | $r['title'] = htmlspecialchars( $r['title'] );
90 | $r['note'] = noteMarkup($r['note'], true);
91 | $r['_descr'] = implode("
", htmlarray($a)). "
". $r['note'];
92 | $r['_title'] = "#". (int)$r['id']. ": ". $r['title'];
93 | $r['_d'] = gmdate('r', $r[$field]);
94 | $r['_field'] = $field;
95 | $data[] = $r;
96 | }
97 | }
98 |
99 | function printRss(array $data, array $listData)
100 | {
101 | $lang = Lang::instance();
102 | $link = get_mttinfo('url'). "?list=". (int)$listData['id'];
103 | $buildDate = gmdate('r');
104 |
105 | $s = "\n".
106 | "\n".
107 | "\n".
108 | "$listData[_feed_title]\n".
109 | "$link\n".
110 | "\n".
111 | "$listData[_feed_descr]\n".
112 | "$buildDate\n\n";
113 |
114 | foreach($data as $v)
115 | {
116 | $guid = $listData['_feed_type']. '-'. $listData['id']. '-'. $v['id']. '-'. $v[$v['_field']];
117 | $itemLink = $link. "&task=". (int)$v['id'];
118 |
119 | $status = '';
120 | if ( $listData['_feed_type'] == 'status' ) {
121 | if ( $v['_field'] == 'd_created' ) {
122 | $status = $lang->get('feed_status_new');
123 | }
124 | elseif ( $v['_field'] == 'd_edited' ) {
125 | $status = $lang->get('feed_status_updated');
126 | }
127 | elseif ( $v['_field'] == 'd_completed' ) {
128 | $status = $lang->get('feed_status_completed');
129 | }
130 | }
131 | if ( $status !='' ) $status = "[$status] ";
132 |
133 | $s .= "\t- \n".
134 | "\t\t". $status. $v['title']. "\n".
135 | "\t\t". $itemLink. "\n".
136 | "\t\t". $v['_d']. "\n".
137 | "\t\t\n".
138 | "\t\t$guid\n".
139 | "\t
\n\n";
140 | }
141 |
142 | $s .= "\n";
143 |
144 | header("Content-type: text/xml; charset=utf-8");
145 | print $s;
146 | }
147 |
--------------------------------------------------------------------------------
/src/includes/lang/pt-pt.json:
--------------------------------------------------------------------------------
1 | {
2 | "_header": {
3 | "ver": "v1.3.6",
4 | "date": "2010-07-29",
5 | "language": "Portuguese (Portugal)",
6 | "original_name": "Português (Europeu)",
7 | "author": "Sérgio Martins",
8 | "author_email": "eurospem@live.com.pt"
9 | },
10 | "My Tiny Todolist": "Minha lista de tarefas",
11 | "htab_newtask": "Nova tarefa",
12 | "htab_search": "Pesquisar",
13 | "btn_add": "Adicionar",
14 | "btn_search": "Pesquisar",
15 | "advanced_add": "Avançado",
16 | "searching": "Pesquisar por",
17 | "tasks": "Tarefas",
18 | "taskdate_inline_created": "created at %s",
19 | "taskdate_inline_completed": "Completed at %s",
20 | "taskdate_inline_duedate": "Due %s",
21 | "taskdate_created": "Data da criação",
22 | "taskdate_completed": "Data da conclusão",
23 | "edit_task": "Editar Tarefa",
24 | "add_task": "Nova Tarefa",
25 | "priority": "Prioridade",
26 | "task": "Tarefa",
27 | "note": "Nota",
28 | "tags": "Etiquetas",
29 | "save": "Guardar",
30 | "cancel": "Cancelar",
31 | "password": "Senha",
32 | "btn_login": "Login",
33 | "a_login": "Login",
34 | "a_logout": "Sair",
35 | "public_tasks": "Tarefa Pública",
36 | "tagcloud": "Tags",
37 | "tagfilter_cancel": "cancelar filtro",
38 | "sortByHand": "Ordenar manualmente",
39 | "sortByPriority": "Ordenar por prioridade",
40 | "sortByDueDate": "Ordenar por data de vencimento",
41 | "sortByDateCreated": "Sort by date created",
42 | "sortByDateModified": "Sort by date modified",
43 | "due": "Prazo",
44 | "daysago": "%d dias atrás",
45 | "indays": "em %d dias",
46 | "months_short": [
47 | "Jan",
48 | "Fev",
49 | "Mar",
50 | "Abr",
51 | "Mai",
52 | "Jun",
53 | "Jul",
54 | "Ago",
55 | "Set",
56 | "Out",
57 | "Nov",
58 | "Dez"
59 | ],
60 | "months_long": [
61 | "Janeiro",
62 | "Fevereiro",
63 | "Março",
64 | "Abril",
65 | "Maio",
66 | "Junho",
67 | "Julho",
68 | "Agosto",
69 | "Setembro",
70 | "Outubro",
71 | "Novembro",
72 | "Dezembro"
73 | ],
74 | "days_min": [
75 | "Dom",
76 | "Seg",
77 | "Ter",
78 | "Qua",
79 | "Qui",
80 | "Sex",
81 | "Sab"
82 | ],
83 | "days_long": [
84 | "Domingo",
85 | "Segunda",
86 | "Terça",
87 | "Quarta",
88 | "Quinta",
89 | "Sexta",
90 | "Sábado"
91 | ],
92 | "today": "hoje",
93 | "yesterday": "ontem",
94 | "tomorrow": "amanhã",
95 | "f_past": "Vencidas",
96 | "f_today": "Hoje e Amanhã",
97 | "f_soon": "Brevemente",
98 | "action_edit": "Editar",
99 | "action_note": "Editar Nota",
100 | "action_delete": "Apagar",
101 | "action_priority": "Prioridade",
102 | "action_move": "Mover para",
103 | "notes": "Notas:",
104 | "notes_show": "Mostrar",
105 | "notes_hide": "Esconder",
106 | "list_new": "Nova lista",
107 | "list_rename": "Renomear lista",
108 | "list_delete": "Apagar lista",
109 | "list_publish": "Publicar lista",
110 | "list_showcompleted": "Mostrar tarefas completas",
111 | "list_clearcompleted": "Limpar tarefas completas",
112 | "list_select": "Select list",
113 | "list_export": "Export",
114 | "list_export_csv": "CSV",
115 | "list_export_ical": "iCalendar",
116 | "list_rssfeed": "RSS Feed",
117 | "alltags": "Todos os títulos:",
118 | "alltags_show": "Mostrar todos",
119 | "alltags_hide": "Esconder todas",
120 | "a_settings": "Configurações",
121 | "rss_feed": "RSS Feed",
122 | "feed_title": "%s",
123 | "feed_completed_tasks": "Completed tasks",
124 | "feed_modified_tasks": "Modified tasks",
125 | "feed_new_tasks": "New tasks",
126 | "alltasks": "All tasks",
127 | "set_header": "Configurações",
128 | "set_title": "Título",
129 | "set_title_descr": "(especifique se deseja alterar o título padrão)",
130 | "set_language": "Idíoma",
131 | "set_protection": "Proteção por Senha",
132 | "set_enabled": "Habilitar",
133 | "set_disabled": "Desabilitar",
134 | "set_newpass": "Nova senha",
135 | "set_newpass_descr": "(deixe em branco se não vai alterar a senha atual)",
136 | "set_smartsyntax": "Smart syntax",
137 | "set_smartsyntax_descr": "(/prioridade/ tarefa /etiquetas/)",
138 | "set_timezone": "Time zone",
139 | "set_autotag": "Etiquetas Auto",
140 | "set_autotag_descr": "(adiciona automáticamente as etiquetas filtradas ás novas tarefas)",
141 | "set_sessions": "Mecanismo de manipulação de sessões",
142 | "set_sessions_php": "PHP",
143 | "set_sessions_files": "Arquivos",
144 | "set_firstdayofweek": "Primeiro dia da semana",
145 | "set_custom": "Custom",
146 | "set_date": "Formato da data",
147 | "set_date2": "Short Date format",
148 | "set_shortdate": "Formato de data abreviada",
149 | "set_clock": "Formato do relógio",
150 | "set_12hour": "12-horas",
151 | "set_24hour": "24-horas",
152 | "set_submit": "Guardar",
153 | "set_cancel": "Cancelar",
154 | "set_showdate": "Mostrar a data na lista de tarefas",
155 | "confirmDelete": "Apagar esta tarefa?",
156 | "confirmLeave": "There can be unsaved data. Do you really want to leave?",
157 | "actionNoteSave": "guardar",
158 | "actionNoteCancel": "cancelar",
159 | "error": "Erro (ver detalhes)",
160 | "denied": "Acesso negado",
161 | "invalidpass": "Senha errada",
162 | "addList": "Criar nova lista",
163 | "addListDefault": "Todo",
164 | "renameList": "Renomear lista",
165 | "deleteList": "Apagar a lista de tarefas toda.\nTem certeza?",
166 | "clearCompleted": "Apagar todas as tarefas completas na lista.\nTem certeza?",
167 | "settingsSaved": "Configurações guardadas. Recarregando..."
168 | }
169 |
--------------------------------------------------------------------------------
/src/includes/lang/hu.json:
--------------------------------------------------------------------------------
1 | {
2 | "_header": {
3 | "ver": "v1.3.2",
4 | "date": "2010-01-20",
5 | "language": "Hungarian",
6 | "original_name": "Magyar",
7 | "author": "Jozsef Kollar",
8 | "author_url": "http://www.bassline.hu"
9 | },
10 | "My Tiny Todolist": "My Tiny Todolist",
11 | "htab_newtask": "Új Feladat",
12 | "htab_search": "Keresés",
13 | "btn_add": "Hozzáad",
14 | "btn_search": "Keresés",
15 | "advanced_add": "Részletes",
16 | "searching": "A következő keresése",
17 | "tasks": "Feladatok",
18 | "taskdate_inline_created": "created at %s",
19 | "taskdate_inline_completed": "Completed at %s",
20 | "taskdate_inline_duedate": "Due %s",
21 | "taskdate_created": "Létrehozás dátuma",
22 | "taskdate_completed": "Befejezés dátuma",
23 | "edit_task": "Feladat szerkesztése",
24 | "add_task": "Új Feladat",
25 | "priority": "Prioritás",
26 | "task": "Feladat",
27 | "note": "Megjegyzés",
28 | "tags": "Cimkék",
29 | "save": "Mentés",
30 | "cancel": "Mégse",
31 | "password": "Jelszó",
32 | "btn_login": "Belépés",
33 | "a_login": "Belépés",
34 | "a_logout": "Kilépés",
35 | "public_tasks": "Közös Feladatok",
36 | "tagcloud": "Tags",
37 | "tagfilter_cancel": "szűrő kikapcsolása",
38 | "sortByHand": "Rendezés kézzel",
39 | "sortByPriority": "Rendezés prioritás szerint",
40 | "sortByDueDate": "Rendezés dátum szerint",
41 | "sortByDateCreated": "Sort by date created",
42 | "sortByDateModified": "Sort by date modified",
43 | "due": "Esedékes",
44 | "daysago": "%d nappal ezelött",
45 | "indays": "%d nap múlva",
46 | "months_short": [
47 | "Jan",
48 | "Feb",
49 | "Már",
50 | "Ápr",
51 | "Máj",
52 | "Jún",
53 | "Júl",
54 | "Aug",
55 | "Szep",
56 | "Okt",
57 | "Nov",
58 | "Dec"
59 | ],
60 | "months_long": [
61 | "Január",
62 | "Február",
63 | "Március",
64 | "Április",
65 | "Május",
66 | "Június",
67 | "Július",
68 | "Augusztus",
69 | "Szeptember",
70 | "Október",
71 | "November",
72 | "December"
73 | ],
74 | "days_min": [
75 | "V",
76 | "H",
77 | "K",
78 | "Sz",
79 | "Cs",
80 | "P",
81 | "Sz"
82 | ],
83 | "days_long": [
84 | "Vasárnap",
85 | "Hétfő",
86 | "Kedd",
87 | "Szerda",
88 | "Csütörtök",
89 | "Péntek",
90 | "Szombat"
91 | ],
92 | "today": "Ma",
93 | "yesterday": "Tegnap",
94 | "tomorrow": "Holnap",
95 | "f_past": "Lejárt",
96 | "f_today": "Ma és holnap",
97 | "f_soon": "Hamarosan",
98 | "action_edit": "Szerkesztés",
99 | "action_note": "Megjegyzés szerkesztése",
100 | "action_delete": "Törlés",
101 | "action_priority": "Prioritás",
102 | "action_move": "Mozgatás ide",
103 | "notes": "Megjegyzés:",
104 | "notes_show": "Mutat",
105 | "notes_hide": "Elrejt",
106 | "list_new": "Új lista",
107 | "list_rename": "Lista átnevezése",
108 | "list_delete": "Lista törlése",
109 | "list_publish": "Lista közzététele",
110 | "list_showcompleted": "Show completed tasks",
111 | "list_clearcompleted": "Clear completed tasks",
112 | "list_select": "Select list",
113 | "list_export": "Export",
114 | "list_export_csv": "CSV",
115 | "list_export_ical": "iCalendar",
116 | "list_rssfeed": "RSS Feed",
117 | "alltags": "Összes Cimke:",
118 | "alltags_show": "Összes mutatása",
119 | "alltags_hide": "Összes elrejtése",
120 | "a_settings": "Beállítások",
121 | "rss_feed": "RSS",
122 | "feed_title": "%s",
123 | "feed_completed_tasks": "Completed tasks",
124 | "feed_modified_tasks": "Modified tasks",
125 | "feed_new_tasks": "New tasks",
126 | "alltasks": "All tasks",
127 | "set_header": "Beállítások",
128 | "set_title": "Cím",
129 | "set_title_descr": "(add meg ha megszeretnéd változtatni az alapértelmezett címet)",
130 | "set_language": "Nyelv",
131 | "set_protection": "Védelem jelszóval",
132 | "set_enabled": "Bekapcsolva",
133 | "set_disabled": "Kikapcsolva",
134 | "set_newpass": "Új jelszó",
135 | "set_newpass_descr": "(hagyd üresen ha nem szeretnéd megváltoztatni a jelenlegi jelszót)",
136 | "set_smartsyntax": "Gyors sorrend",
137 | "set_smartsyntax_descr": "(/prioritás/ feladatok / cimkék/)",
138 | "set_timezone": "Time zone",
139 | "set_autotag": "Autómatikus cimkézés",
140 | "set_autotag_descr": "(automatikusan az aktuális cimke hozzárendelése)",
141 | "set_sessions": "a feladat kezelés módja",
142 | "set_sessions_php": "PHP",
143 | "set_sessions_files": "Files",
144 | "set_firstdayofweek": "A hét első napja",
145 | "set_custom": "Custom",
146 | "set_date": "Dátum formátum",
147 | "set_date2": "Short Date format",
148 | "set_shortdate": "Rövid dátum formátum",
149 | "set_clock": "Óra formátum",
150 | "set_12hour": "12-óra",
151 | "set_24hour": "24-óra",
152 | "set_submit": "Változások mentése",
153 | "set_cancel": "Mégse",
154 | "set_showdate": "Show task date in list",
155 | "confirmDelete": "Biztosan törölni akarod a feladatot?",
156 | "confirmLeave": "There can be unsaved data. Do you really want to leave?",
157 | "actionNoteSave": "Mentés",
158 | "actionNoteCancel": "Mégse",
159 | "error": "Hiba lépett fel (kattints a részletekért)",
160 | "denied": "Hozzáférés megtagadva",
161 | "invalidpass": "Hibás jelszó",
162 | "addList": "Új Lista",
163 | "addListDefault": "Todo",
164 | "renameList": "Lista átnevezése",
165 | "deleteList": "Az aktuális Lista törlése az összes Feladattal.\nBiztos benne?",
166 | "clearCompleted": "This will delete all completed tasks in the list.\nAre you sure?",
167 | "settingsSaved": "Beállítások mentve. Újratöltés..."
168 | }
169 |
--------------------------------------------------------------------------------