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(){