diff --git a/src/api.php b/src/api.php index 64816d4..3dc07c6 100644 --- a/src/api.php +++ b/src/api.php @@ -180,7 +180,7 @@ function checkWriteAccess(?int $listId = null) jsonExit( array('total'=>0, 'list'=>array(), 'denied'=>1) ); } -function haveWriteAccess(?int $listId = null) +function haveWriteAccess(?int $listId = null) : bool { if (is_readonly()) { return false; diff --git a/src/content/mytinytodo.js b/src/content/mytinytodo.js index 38a7cdc..78cb13a 100644 --- a/src/content/mytinytodo.js +++ b/src/content/mytinytodo.js @@ -940,9 +940,9 @@ var mytinytodo = window.mytinytodo = _mtt = { urlForFeed: function(list) { - var l = list || curList; - if (l === undefined) return ''; - return _mtt.mttUrl + 'feed.php?list='+l.id; + list = list || curList; + if (list === undefined) return ''; + return _mtt.mttUrl + 'feed.php?list=' + list.id; }, urlForSettings: function(json = 0) @@ -1018,6 +1018,35 @@ function publishCurList() }); }; +function enableFeedKeyInCurList() +{ + if (!curList) return false; + _mtt.db.request('enableFeedKey', { + list: curList.id, + enable: (curList.feedKey === undefined || curList.feedKey === '') ? 1 : 0 + }, function(json){ + if (!parseInt(json.total)) return; + var item = json.list[0]; + curList.feedKey = item.feedKey; + if (curList.feedKey) { + $('#btnFeedKey').addClass('mtt-item-checked'); + $('#btnShowFeedKey').removeClass('mtt-item-disabled'); + alert(curList.feedKey); + } + else { + $('#btnFeedKey').removeClass('mtt-item-checked'); + $('#btnShowFeedKey').addClass('mtt-item-disabled'); + } + }); +}; + +function showFeedKeyInCurList() +{ + if (!curList) return false; + if (curList.feedKey === undefined || curList.feedKey === '') return false; + alert(curList.feedKey); +}; + function loadTasks(opts) { @@ -1504,6 +1533,8 @@ function listMenuClick(el, menu) case 'btnRenameList': renameCurList(); break; case 'btnDeleteList': deleteCurList(); break; case 'btnPublish': publishCurList(); break; + case 'btnFeedKey': enableFeedKeyInCurList(); break; + case 'btnShowFeedKey': showFeedKeyInCurList(); break; case 'btnHideList': hideList(curList.id); break; case 'btnExportCSV': return true; case 'btnExportICAL': return true; @@ -2258,7 +2289,7 @@ function cmenuOnListHidden(list) function tabmenuOnListSelected(list) { - if(list.published) { + if (list.published) { $('#btnPublish').addClass('mtt-item-checked'); $('#btnRssFeed').removeClass('mtt-item-disabled'); } @@ -2266,8 +2297,20 @@ function tabmenuOnListSelected(list) $('#btnPublish').removeClass('mtt-item-checked'); $('#btnRssFeed').addClass('mtt-item-disabled'); } - if(list.showCompl) $('#btnShowCompleted').addClass('mtt-item-checked'); - else $('#btnShowCompleted').removeClass('mtt-item-checked'); + if (list.showCompl) { + $('#btnShowCompleted').addClass('mtt-item-checked'); + } + else { + $('#btnShowCompleted').removeClass('mtt-item-checked'); + } + if (list.feedKey !== undefined && list.feedKey !== '') { + $('#btnFeedKey').addClass('mtt-item-checked'); + $('#btnShowFeedKey').removeClass('mtt-item-disabled'); + } + else { + $('#btnFeedKey').removeClass('mtt-item-checked'); + $('#btnShowFeedKey').addClass('mtt-item-disabled'); + } }; diff --git a/src/content/mytinytodo_api.js b/src/content/mytinytodo_api.js index 1a7a888..ccba772 100644 --- a/src/content/mytinytodo_api.js +++ b/src/content/mytinytodo_api.js @@ -296,6 +296,21 @@ MytinytodoAjaxApi.prototype = }); }, + enableFeedKey: function(params, callback) + { + $.ajax({ + url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list), + method: 'PUT', + contentType : 'application/json', + data: JSON.stringify({ + action: 'enableFeedKey', + enable: params.enable, + }), + success: callback, + dataType: 'json' + }); + }, + setShowNotesInList: function(params, callback) { $.ajax({ diff --git a/src/export.php b/src/export.php index f950fb5..0d7d83e 100644 --- a/src/export.php +++ b/src/export.php @@ -9,14 +9,19 @@ //$dontStartSession = 1; require_once('./init.php'); -$onlyPublishedList = false; -if(!have_write_access()) $onlyPublishedList = true; - $listId = (int)_get('list'); $db = DBConnection::instance(); -$listData = $db->sqa("SELECT * FROM {$db->prefix}lists WHERE id=$listId ". ($onlyPublishedList ? "AND published=1" : "") ); -if(!$listData) { - die("No such list or access denied"); +$listData = $db->sqa("SELECT * FROM {$db->prefix}lists WHERE id=$listId"); +if ( $listData && !is_logged() && !$listData['published'] ) { + $extra = json_decode($listData['extra'] ?? '', true, 10, JSON_INVALID_UTF8_SUBSTITUTE); + $feedKey = (string)$extra['feedKey'] ?? ''; + $inFeedKey = trim(_get('key')); + if ($feedKey == '' || $feedKey != $inFeedKey) { + die("Access denied."); + } +} +if (!$listData) { + die("No list found."); } $sqlSort = "ORDER BY compl ASC, "; @@ -37,13 +42,6 @@ if($format == 'ical') printICal($listData, $data); else printCSV($listData, $data); -function have_write_access() -{ - if(is_logged()) return true; - return false; -} - - function printCSV($listData, $data) { $s = "\xEF\xBB\xBF". "Completed;Priority;Task;Notes;Tags;Due;DateCreated;DateCompleted\n"; diff --git a/src/feed.php b/src/feed.php index b6b7378..356af99 100644 --- a/src/feed.php +++ b/src/feed.php @@ -15,11 +15,16 @@ $lang = Lang::instance(); $listId = (int)_get('list'); $db = DBConnection::instance(); $listData = $db->sqa("SELECT * FROM {$db->prefix}lists WHERE id=$listId"); -if (need_auth() && (!$listData || !$listData['published'])) { - die("Access denied!
List is not published."); +if ( $listData && need_auth() && !$listData['published'] ) { + $extra = json_decode($listData['extra'] ?? '', true, 10, JSON_INVALID_UTF8_SUBSTITUTE); + $feedKey = (string)$extra['feedKey'] ?? ''; + $inFeedKey = trim(_get('key')); + if ($feedKey == '' || $feedKey != $inFeedKey) { + die("Access denied!
List is not published."); + } } -if(!$listData) { - die("No list found"); +if (!$listData) { + die("No list found."); } $data = array(); diff --git a/src/includes/api/ListsController.php b/src/includes/api/ListsController.php index 61fc6c3..cd716ed 100644 --- a/src/includes/api/ListsController.php +++ b/src/includes/api/ListsController.php @@ -19,7 +19,8 @@ class ListsController extends ApiController { check_token(); $t = array(); $t['total'] = 0; - if (!is_logged()) { + $haveWriteAccess = haveWriteAccess(); + if (!$haveWriteAccess) { $sqlWhere = 'WHERE published=1'; } else { @@ -31,7 +32,7 @@ class ListsController extends ApiController { while ($r = $q->fetchAssoc()) { $t['total']++; - $t['list'][] = $this->prepareList($r); + $t['list'][] = $this->prepareList($r, $haveWriteAccess); } return $t; } @@ -60,7 +61,7 @@ class ListsController extends ApiController { $id = $db->lastInsertId(); $t['total'] = 1; $r = $db->sqa("SELECT * FROM {$db->prefix}lists WHERE id=$id"); - $t['list'][] = $this->prepareList($r); + $t['list'][] = $this->prepareList($r, true); return $t; } @@ -96,7 +97,7 @@ class ListsController extends ApiController { if (!$r) { return null; } - $t = $this->prepareList($r); + $t = $this->prepareList($r, haveWriteAccess()); return $t; } @@ -142,6 +143,7 @@ class ListsController extends ApiController { case 'rename': return $this->renameList($id); break; case 'sort': return $this->sortList($id); break; case 'publish': return $this->publishList($id); break; + case 'enableFeedKey': return $this->enableFeedKey($id); break; case 'showNotes': return $this->showNotes($id); break; case 'hide': return $this->hideList($id); break; case 'clearCompleted': return $this->clearCompleted($id); break; @@ -172,12 +174,23 @@ class ListsController extends ApiController { 'showCompl' => $showCompleted, 'showNotes' => 0, 'hidden' => $hidden, + 'feedKey' => '', ); } - private function prepareList($row) + private function prepareList($row, bool $haveWriteAccess) { $taskview = (int)$row['taskview']; + $feedKey = ''; + if ($haveWriteAccess) { + $extra = json_decode($row['extra'] ?? '', true, 10, JSON_INVALID_UTF8_SUBSTITUTE); + if ($extra === false) { + error_log("Failed to decodes JSON data of list extra listId=". (int)$row['id'] . ": " . json_last_error_msg()); + $extra = []; + } + $feedKey = (string)$extra['feedKey'] ?? ''; + } + return array( 'id' => $row['id'], 'name' => htmlarray($row['name']), @@ -186,6 +199,7 @@ class ListsController extends ApiController { 'showCompl' => $taskview & 1 ? 1 : 0, 'showNotes' => $taskview & 2 ? 1 : 0, 'hidden' => $taskview & 4 ? 1 : 0, + 'feedKey' => $feedKey, ); } @@ -202,7 +216,7 @@ class ListsController extends ApiController { $db->dq("UPDATE {$db->prefix}lists SET name=?,d_edited=? WHERE id=$id", array($name, time()) ); $t['total'] = $db->affected(); $r = $db->sqa("SELECT * FROM {$db->prefix}lists WHERE id=$id"); - $t['list'][] = $this->prepareList($r); + $t['list'][] = $this->prepareList($r, true); return $t; } @@ -250,6 +264,33 @@ class ListsController extends ApiController { return ['total'=>1]; } + private function enableFeedKey(int $listId) + { + $db = DBConnection::instance(); + $flag = (int)($this->req->jsonBody['enable'] ?? 0); + $json = $db->sq("SELECT extra FROM {$db->prefix}lists WHERE id=$listId") ?? ''; + $extra = strlen($json) > 0 ? json_decode($json, true, 10, JSON_INVALID_UTF8_SUBSTITUTE) : []; + if ($extra === false) { + error_log("Failed to decodes JSON data of list extra listId=$listId: " . json_last_error_msg()); + $extra = []; + } + if ($flag == 0) { + $extra['feedKey'] = ''; + } + else { + $extra['feedKey'] = randomString(); + } + $json = json_encode($extra, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + $db->ex("UPDATE {$db->prefix}lists SET extra=?,d_edited=? WHERE id=$listId", array($json, time())); + return [ + 'total' => 1, + 'list' => [[ + 'id' => $listId, + 'feedKey' => $extra['feedKey'] + ]] + ]; + } + private function showNotes(int $listId) { $db = DBConnection::instance(); diff --git a/src/includes/common.php b/src/includes/common.php index 6d005fb..a24f7b2 100644 --- a/src/includes/common.php +++ b/src/includes/common.php @@ -175,3 +175,15 @@ function isValidSignature(string $signature, string $id, string $key, string $sa if ( hash_equals($signature, idSignature($id, $key, $salt)) ) return true; return false; } + + +function randomString(int $len = 16) : string +{ + $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $a = []; + $max = strlen($chars) - 1; + for ($i = 0; $i < $len; $i++) { + $a[]= $chars[random_int(0, $max)]; + } + return implode('', $a); +} diff --git a/src/includes/lang/en.json b/src/includes/lang/en.json index 38f2e33..cb9fd8e 100644 --- a/src/includes/lang/en.json +++ b/src/includes/lang/en.json @@ -123,14 +123,16 @@ "list_new": "New list", "list_rename": "Rename list", "list_delete": "Delete list", - "list_publish": "Publish list", "list_showcompleted": "Show completed tasks", "list_clearcompleted": "Clear completed tasks", "list_select": "Select list", - "list_export": "Export", - "list_export_csv": "CSV", - "list_export_ical": "iCalendar", - "list_rssfeed": "RSS Feed", + "list_share": "Share", + "list_publish": "Publish list", + "list_enable_feedkey": "Enable Feed Key", + "list_show_feedkey": "Show Feed Key", + "list_rssfeed": "RSS Feed", + "list_export_to_csv": "Export to CSV", + "list_export_to_ical": "Export to iCalendar", "list_hide": "Hide list", "alltags": "All tags:", "alltags_show": "Show all", diff --git a/src/includes/lang/ru.json b/src/includes/lang/ru.json index 35ee85a..26db51b 100644 --- a/src/includes/lang/ru.json +++ b/src/includes/lang/ru.json @@ -123,14 +123,16 @@ "list_new": "Новый список", "list_rename": "Переименовать список", "list_delete": "Удалить список", - "list_publish": "Опубликовать список", "list_showcompleted": "Показать завершенные задачи", "list_clearcompleted": "Удалить завершенные задачи", "list_select": "Выбрать список", - "list_export": "Экспортировать", - "list_export_csv": "CSV", - "list_export_ical": "iCalendar", - "list_rssfeed": "RSS-лента", + "list_share": "Поделиться", + "list_publish": "Опубликовать список", + "list_enable_feedkey": "Включить доступ по ключу", + "list_show_feedkey": "Показать ключ", + "list_rssfeed": "RSS-лента", + "list_export_to_csv": "Экспортировать в CSV", + "list_export_to_ical": "Экспортировать в iCalendar", "list_hide": "Скрыть список", "alltags": "Все теги:", "alltags_show": "Показать все", diff --git a/src/includes/theme.php b/src/includes/theme.php index 3681dab..047a7b7 100644 --- a/src/includes/theme.php +++ b/src/includes/theme.php @@ -221,11 +221,9 @@ $().ready(function(){
  • -
  • -
  • -
  • +
  • @@ -237,10 +235,15 @@ $().ready(function(){ -