* implement feed key to allow to get an export or rss of password-protected lists (just add &key=YOUR_KEY to the url)

This commit is contained in:
maxpozdeev 2022-08-11 17:59:57 +03:00
parent 4d481d6013
commit 46de0c09de
10 changed files with 167 additions and 46 deletions

View file

@ -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;

View file

@ -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');
}
};

View file

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

View file

@ -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";

View file

@ -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!<br> 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!<br> List is not published.");
}
}
if(!$listData) {
die("No list found");
if (!$listData) {
die("No list found.");
}
$data = array();

View file

@ -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();

View file

@ -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);
}

View file

@ -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",

View file

@ -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": "Показать все",

View file

@ -221,11 +221,9 @@ $().ready(function(){
<li class="mtt-need-list mtt-need-real-list" id="btnRenameList"><?php _e('list_rename');?></li>
<li class="mtt-need-list mtt-need-real-list" id="btnDeleteList"><?php _e('list_delete');?></li>
<li class="mtt-need-list mtt-need-real-list" id="btnClearCompleted"><?php _e('list_clearcompleted');?></li>
<li class="mtt-need-list mtt-need-real-list mtt-menu-indicator" submenu="listexportmenucontainer"><div class="submenu-icon"></div><?php _e('list_export'); ?></li>
<li class="mtt-need-list" id="btnHideList"><?php _e('list_hide');?></li>
<li class="mtt-menu-delimiter"></li>
<li class="mtt-need-list mtt-need-real-list" id="btnPublish"><div class="menu-icon"></div><?php _e('list_publish');?></li>
<li class="mtt-need-list mtt-need-real-list" id="btnRssFeed"><div class="menu-icon"></div><a href="#"><?php _e('list_rssfeed');?></a></li>
<li class="mtt-need-list mtt-need-real-list mtt-menu-indicator" submenu="listsharemenucontainer"><div class="submenu-icon"></div><?php _e('list_share'); ?></li>
<li class="mtt-menu-delimiter mtt-need-real-list"></li>
<li class="mtt-need-list mtt-need-real-list sort-item" id="sortByHand"><div class="menu-icon"></div><?php _e('sortByHand');?> <span class="mtt-sort-direction"></span></li>
<li class="mtt-need-list sort-item" id="sortByDateCreated"><div class="menu-icon"></div><?php _e('sortByDateCreated');?> <span class="mtt-sort-direction"></span></li>
@ -237,10 +235,15 @@ $().ready(function(){
</ul>
</div>
<div id="listexportmenucontainer" class="mtt-menu-container" style="display:none">
<div id="listsharemenucontainer" class="mtt-menu-container" style="display:none">
<ul>
<li class="mtt-need-list mtt-need-real-list" id="btnExportCSV"><a href="#"><?php _e('list_export_csv');?></a></li>
<li class="mtt-need-list mtt-need-real-list" id="btnExportICAL"><a href="#"><?php _e('list_export_ical');?></a></li>
<li class="mtt-need-list mtt-need-real-list" id="btnPublish"><div class="menu-icon"></div><?php _e('list_publish');?></li>
<li class="mtt-need-list mtt-need-real-list" id="btnRssFeed"><div class="menu-icon"></div><a href="#"><?php _e('list_rssfeed');?></a></li>
<li class="mtt-need-list mtt-need-real-list" id="btnExportCSV"><a href="#"><?php _e('list_export_to_csv');?></a></li>
<li class="mtt-need-list mtt-need-real-list" id="btnExportICAL"><a href="#"><?php _e('list_export_to_ical');?></a></li>
<li class="mtt-menu-delimiter"></li>
<li class="mtt-need-list mtt-need-real-list" id="btnFeedKey"><div class="menu-icon"></div><?php _e('list_enable_feedkey');?></li>
<li class="mtt-need-list mtt-need-real-list" id="btnShowFeedKey"><div class="menu-icon"></div><?php _e('list_show_feedkey');?></li>
</ul>
</div>