├── 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 | 2 | 3 | -------------------------------------------------------------------------------- /src/content/theme/images/arr-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/content/theme/images/arrdown2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/content/theme/images/select-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/content/theme/images/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/content/theme/images/selectlist.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/content/theme/images/back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/content/theme/images/task-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/content/theme/images/note-toggle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/content/theme/images/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/content/theme/images/task-menu2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/content/theme/images/rss.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | RSS feed icon 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/content/theme/images/search-cancel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | -------------------------------------------------------------------------------- /src/content/theme/images/rss-disabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | RSS feed icon 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /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 = "
"; 42 | $formEnd = "
"; 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 | --------------------------------------------------------------------------------