* change HTTP API

This commit is contained in:
maxpozdeev 2022-07-10 15:23:53 +03:00
parent 90278d0f63
commit 490b64c2b3
14 changed files with 1758 additions and 1162 deletions

12
src/.htaccess Normal file
View file

@ -0,0 +1,12 @@
# For Apache
#<IfModule mod_rewrite.c>
# RewriteEngine On
# RewriteCond %{REQUEST_FILENAME} !-f
# RewriteCond %{REQUEST_FILENAME} !-d
# RewriteRule ^api/(.*)$ api.php/$1 [L,QSA]
#</IfModule>
# For Nginx set something like this:
# location /api/ {
# rewrite ^/api/(.*) /api.php/$1 last;
# }

View file

@ -1,970 +0,0 @@
<?php
/*
This file is a part of myTinyTodo.
(C) Copyright 2009-2011,2019-2022 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
require_once('./init.php');
require_once(MTTINC. 'markup.php');
if (MTT_DEBUG) {
set_error_handler('myErrorHandler'); //catch Notices, Warnings
set_exception_handler('myExceptionHandler');
}
else {
ini_set('display_errors', '0');
}
$db = DBConnection::instance();
if(isset($_GET['loadLists']))
{
check_token();
$t = array();
$t['total'] = 0;
if (!is_logged()) $sqlWhere = 'WHERE published=1';
else {
$sqlWhere = '';
$t['list'][] = prepareAllTasksList(); // show alltasks lists only for authorized user
$t['total'] = 1;
}
$q = $db->dq("SELECT * FROM {$db->prefix}lists $sqlWhere ORDER BY ow ASC, id ASC");
while($r = $q->fetchAssoc())
{
$t['total']++;
$t['list'][] = prepareList($r);
}
jsonExit($t);
}
elseif(isset($_GET['loadTasks']))
{
$listId = (int)_get('list');
check_read_access($listId);
$sqlWhere = $inner = $sqlWhereListId = $sqlInnerWhereListId = '';
if ($listId == -1) {
$userLists = getUserListsSimple();
$sqlWhereListId = "{$db->prefix}todolist.list_id IN (". implode(',', array_keys($userLists)). ") ";
$sqlInnerWhereListId = "list_id IN (". implode(',', array_keys($userLists)). ") ";
}
else {
$sqlWhereListId = "{$db->prefix}todolist.list_id=". $listId;
$sqlInnerWhereListId = "list_id=$listId ";
}
if (_get('compl') == 0) {
$sqlWhere .= ' AND compl=0';
}
$tag = trim(_get('t'));
if($tag != '')
{
$at = explode(',', $tag);
$tagIds = array();
$tagExIds = array();
foreach($at as $i=>$atv) {
$atv = trim($atv);
if($atv == '' || $atv == '^') continue;
if(substr($atv,0,1) == '^') {
$tagExIds[] = getTagId(substr($atv,1));
} else {
$tagIds[] = getTagId($atv);
}
}
// Include tags: All
if (sizeof($tagIds) > 1) {
$inner .= "INNER JOIN (SELECT task_id, COUNT(tag_id) AS c FROM {$db->prefix}tag2task WHERE $sqlInnerWhereListId AND tag_id IN (".
implode(',',$tagIds). ") GROUP BY task_id) AS t2t ON id=t2t.task_id";
$sqlWhere .= " AND c=". sizeof($tagIds);
}
elseif ($tagIds) {
$inner .= "INNER JOIN {$db->prefix}tag2task ON id=task_id";
$sqlWhere .= " AND tag_id = {$tagIds[0]}";
}
// Exclude tags
if (sizeof($tagExIds) > 0) {
$sqlWhere .= " AND {$db->prefix}todolist.id NOT IN (SELECT DISTINCT task_id FROM {$db->prefix}tag2task WHERE $sqlInnerWhereListId AND tag_id IN (".
implode(',',$tagExIds). "))";
}
//no optimization for single exTag
}
$s = trim(_get('s'));
if ($s != '') {
if (preg_match("|^#(\d+)$|", $s, $m)) $sqlWhere .= " AND {$db->prefix}todolist.id = ". (int)$m[1];
else $sqlWhere .= " AND (title LIKE ". $db->quoteForLike("%%%s%%",$s). " OR note LIKE ". $db->quoteForLike("%%%s%%",$s). ")";
}
$sort = (int)_get('sort');
$sqlSort = "ORDER BY compl ASC, ";
if($sort == 1) $sqlSort .= "prio DESC, ddn ASC, duedate ASC, ow ASC"; // byPrio
elseif($sort == 101) $sqlSort .= "prio ASC, ddn DESC, duedate DESC, ow DESC"; // byPrio (reverse)
elseif($sort == 2) $sqlSort .= "ddn ASC, duedate ASC, prio DESC, ow ASC"; // byDueDate
elseif($sort == 102) $sqlSort .= "ddn DESC, duedate DESC, prio ASC, ow DESC";// byDueDate (reverse)
elseif($sort == 3) $sqlSort .= "d_created ASC, prio DESC, ow ASC"; // byDateCreated
elseif($sort == 103) $sqlSort .= "d_created DESC, prio ASC, ow DESC"; // byDateCreated (reverse)
elseif($sort == 4) $sqlSort .= "d_edited ASC, prio DESC, ow ASC"; // byDateModified
elseif($sort == 104) $sqlSort .= "d_edited DESC, prio ASC, ow DESC"; // byDateModified (reverse)
else $sqlSort .= "ow ASC";
$t = array();
$t['total'] = 0;
$t['list'] = array();
$q = $db->dq("SELECT *, duedate IS NULL AS ddn FROM {$db->prefix}todolist $inner WHERE $sqlWhereListId $sqlWhere $sqlSort");
while($r = $q->fetchAssoc())
{
$t['total']++;
$t['list'][] = prepareTaskRow($r);
}
if(_get('setCompl') && have_write_access($listId)) {
$bitwise = (_get('compl') == 0) ? 'taskview & ~1' : 'taskview | 1';
$db->dq("UPDATE {$db->prefix}lists SET taskview=$bitwise WHERE id=$listId");
}
jsonExit($t);
}
elseif(isset($_GET['newTask']))
{
$listId = (int)_post('list');
check_write_access($listId);
$t = array();
$t['total'] = 0;
$title = trim(_post('title'));
$prio = 0;
$tags = '';
if(Config::get('smartsyntax') != 0)
{
$a = parse_smartsyntax($title);
if($a === false) {
jsonExit($t);
}
$title = $a['title'];
$prio = $a['prio'];
$tags = $a['tags'];
}
if($title == '') {
jsonExit($t);
}
if(Config::get('autotag')) $tags .= ','._post('tag');
$ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}todolist WHERE list_id=$listId AND compl=0");
$db->ex("BEGIN");
$db->dq("INSERT INTO {$db->prefix}todolist (uuid,list_id,title,d_created,d_edited,ow,prio) VALUES (?,?,?,?,?,?,?)",
array(generateUUID(), $listId, $title, time(), time(), $ow, $prio) );
$id = $db->lastInsertId();
if($tags != '')
{
$aTags = prepareTags($tags);
if($aTags) {
addTaskTags($id, $aTags['ids'], $listId);
$db->ex("UPDATE {$db->prefix}todolist SET tags=?,tags_ids=? WHERE id=$id", array(implode(',',$aTags['tags']), implode(',',$aTags['ids'])));
}
}
$db->ex("COMMIT");
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
$t['list'][] = prepareTaskRow($r);
$t['total'] = 1;
jsonExit($t);
}
elseif(isset($_GET['fullNewTask']))
{
$listId = (int)_post('list');
check_write_access($listId);
$title = trim(_post('title'));
$note = str_replace("\r\n", "\n", _post('note'));
$prio = (int)_post('prio');
if($prio < -1) $prio = -1;
elseif($prio > 2) $prio = 2;
$duedate = parse_duedate(trim(_post('duedate')));
$t = array();
$t['total'] = 0;
if($title == '') {
jsonExit($t);
}
$tags = trim(_post('tags'));
if(Config::get('autotag')) $tags .= ','._post('tag');
$ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}todolist WHERE list_id=$listId AND compl=0");
$db->ex("BEGIN");
$db->dq("INSERT INTO {$db->prefix}todolist (uuid,list_id,title,d_created,d_edited,ow,prio,note,duedate) VALUES(?,?,?,?,?,?,?,?,?)",
array(generateUUID(), $listId, $title, time(), time(), $ow, $prio, $note, $duedate) );
$id = $db->lastInsertId();
if($tags != '')
{
$aTags = prepareTags($tags);
if($aTags) {
addTaskTags($id, $aTags['ids'], $listId);
$db->ex("UPDATE {$db->prefix}todolist SET tags=?,tags_ids=? WHERE id=$id", array(implode(',',$aTags['tags']), implode(',',$aTags['ids'])));
}
}
$db->ex("COMMIT");
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
$t['list'][] = prepareTaskRow($r);
$t['total'] = 1;
jsonExit($t);
}
elseif(isset($_GET['deleteTask']))
{
$id = (int)_post('id');
$deleted = deleteTask($id);
$t = array();
$t['total'] = $deleted;
$t['list'][] = array('id'=>$id);
jsonExit($t);
}
elseif(isset($_GET['completeTask']))
{
check_write_access();
$id = (int)_post('id');
$compl = _post('compl') ? 1 : 0;
$listId = (int)$db->sq("SELECT list_id FROM {$db->prefix}todolist WHERE id=$id");
if($compl) $ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}todolist WHERE list_id=$listId AND compl=1");
else $ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}todolist WHERE list_id=$listId AND compl=0");
$dateCompleted = $compl ? time() : 0;
$db->dq("UPDATE {$db->prefix}todolist SET compl=$compl,ow=$ow,d_completed=?,d_edited=? WHERE id=$id",
array($dateCompleted, time()) );
$t = array();
$t['total'] = 1;
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
$t['list'][] = prepareTaskRow($r);
jsonExit($t);
}
elseif(isset($_GET['editNote']))
{
check_write_access();
$id = (int)_post('id');
$note = str_replace("\r\n", "\n", _post('note'));
$db->dq("UPDATE {$db->prefix}todolist SET note=?,d_edited=? WHERE id=$id", array($note, time()) );
$t = array();
$t['total'] = 1;
$t['list'][] = array('id'=>$id, 'note'=> noteMarkup($note), 'noteText'=>(string)$note);
jsonExit($t);
}
elseif(isset($_GET['editTask']))
{
check_write_access();
$id = (int)_post('id');
$title = trim(_post('title'));
$note = str_replace("\r\n", "\n", _post('note'));
$prio = (int)_post('prio');
if($prio < -1) $prio = -1;
elseif($prio > 2) $prio = 2;
$duedate = parse_duedate(trim(_post('duedate')));
$t = array();
$t['total'] = 0;
if($title == '') {
jsonExit($t);
}
$listId = $db->sq("SELECT list_id FROM {$db->prefix}todolist WHERE id=$id");
$tags = trim(_post('tags'));
$db->ex("BEGIN");
$db->ex("DELETE FROM {$db->prefix}tag2task WHERE task_id=$id");
$aTags = prepareTags($tags);
if($aTags) {
$tags = implode(',', $aTags['tags']);
$tags_ids = implode(',',$aTags['ids']);
addTaskTags($id, $aTags['ids'], $listId);
}
$db->dq("UPDATE {$db->prefix}todolist SET title=?,note=?,prio=?,tags=?,tags_ids=?,duedate=?,d_edited=? WHERE id=$id",
array($title, $note, $prio, $tags, $tags_ids, $duedate, time()) );
$db->ex("COMMIT");
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
if($r) {
$t['list'][] = prepareTaskRow($r);
$t['total'] = 1;
}
jsonExit($t);
}
elseif(isset($_GET['changeOrder']))
{
check_write_access();
$s = _post('order');
parse_str($s, $order);
$t = array();
$t['total'] = 0;
if($order)
{
$ad = array();
foreach($order as $id=>$diff) {
$ad[(int)$diff][] = (int)$id;
}
$db->ex("BEGIN");
foreach($ad as $diff=>$ids) {
if($diff >=0) $set = "ow=ow+".$diff;
else $set = "ow=ow-".abs($diff);
$db->dq("UPDATE {$db->prefix}todolist SET $set,d_edited=? WHERE id IN (".implode(',',$ids).")", array(time()) );
}
$db->ex("COMMIT");
$t['total'] = 1;
}
jsonExit($t);
}
elseif(isset($_POST['login']))
{
check_token();
$t = array('logged' => 0);
if (!need_auth()) {
$t['disabled'] = 1;
jsonExit($t);
}
if ( isPasswordEqualsToHash(_post('password'), Config::get('password')) ) {
updateSessionLogged(true);
$t['token'] = update_token();
$t['logged'] = 1;
}
jsonExit($t);
}
elseif(isset($_POST['logout']))
{
check_token();
updateSessionLogged(false);
update_token();
session_regenerate_id(1);
$t = array('logged' => 0);
jsonExit($t);
}
elseif(isset($_GET['suggestTags']))
{
$listId = (int)_get('list');
check_read_access($listId);
$begin = trim(_get('q'));
$limit = 8;
$q = $db->dq("SELECT name,id FROM {$db->prefix}tags INNER JOIN {$db->prefix}tag2task ON id=tag_id WHERE list_id=$listId AND name LIKE ".
$db->quoteForLike('%s%%',$begin) ." GROUP BY tag_id ORDER BY name LIMIT $limit");
$t = array();
while($r = $q->fetchRow()) {
$t[] = $r[0];
}
jsonExit($t);
}
elseif(isset($_GET['setTaskPriority']))
{
check_write_access();
$id = (int)_post('id');
$prio = (int)_post('priority');
if($prio < -1) $prio = -1;
elseif($prio > 2) $prio = 2;
$db->ex("UPDATE {$db->prefix}todolist SET prio=$prio,d_edited=? WHERE id=$id", array(time()) );
$t = array();
$t['total'] = 1;
$t['list'][] = array('id'=>$id, 'prio'=>$prio);
jsonExit($t);
}
elseif(isset($_GET['tagCloud']))
{
$listId = (int)_get('list');
check_read_access($listId);
$q = $db->dq("SELECT name,tag_id,COUNT(tag_id) AS tags_count FROM {$db->prefix}tag2task INNER JOIN {$db->prefix}tags ON tag_id=id ".
"WHERE list_id=$listId GROUP BY (tag_id) ORDER BY tags_count ASC");
$at = array();
$ac = array();
while($r = $q->fetchAssoc()) {
$at[] = array('name'=>$r['name'], 'id'=>$r['tag_id']);
$ac[] = $r['tags_count'];
}
$t = array();
$t['total'] = 0;
$count = sizeof($at);
if(!$count) {
jsonExit($t);
}
$qmax = max($ac);
$qmin = min($ac);
if($count >= 10) $grades = 10;
else $grades = $count;
$step = ($qmax - $qmin)/$grades;
foreach($at as $i=>$tag)
{
$t['cloud'][] = array('tag'=>htmlarray($tag['name']), 'id'=>(int)$tag['id'], 'w'=> tag_size($qmin,$ac[$i],$step) );
}
$t['total'] = $count;
jsonExit($t);
}
elseif(isset($_GET['addList']))
{
check_write_access();
$t = array();
$t['total'] = 0;
$name = str_replace( array('"',"'",'<','>','&'), '', trim(_post('name')) );
$ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}lists");
$db->dq("INSERT INTO {$db->prefix}lists (uuid,name,ow,d_created,d_edited,taskview) VALUES (?,?,?,?,?,?)",
array(generateUUID(), $name, $ow, time(), time(), 1) );
$id = $db->lastInsertId();
$t['total'] = 1;
$r = $db->sqa("SELECT * FROM {$db->prefix}lists WHERE id=$id");
$t['list'][] = prepareList($r);
jsonExit($t);
}
elseif(isset($_GET['renameList']))
{
check_write_access();
$t = array();
$t['total'] = 0;
$id = (int)_post('list');
$name = str_replace(array('"',"'",'<','>','&'),array('','','','',''),trim(_post('name')));
$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'][] = prepareList($r);
jsonExit($t);
}
elseif(isset($_GET['deleteList']))
{
check_write_access();
$t = array();
$t['total'] = 0;
$id = (int)_post('list');
$db->ex("BEGIN");
$db->ex("DELETE FROM {$db->prefix}lists WHERE id=$id");
$t['total'] = $db->affected();
if($t['total']) {
$db->ex("DELETE FROM {$db->prefix}tag2task WHERE list_id=$id");
$db->ex("DELETE FROM {$db->prefix}todolist WHERE list_id=$id");
}
$db->ex("COMMIT");
jsonExit($t);
}
elseif(isset($_GET['setSort']))
{
check_write_access();
$listId = (int)_post('list');
$sort = (int)_post('sort');
if($sort < 0 || $sort > 104) $sort = 0;
elseif($sort < 101 && $sort > 4) $sort = 0;
if ($listId == -1) {
$opts = Config::requestDomain('alltasks.json');
$opts['sort'] = $sort;
Config::saveDomain('alltasks.json', $opts);
}
else {
$db->ex("UPDATE {$db->prefix}lists SET sorting=$sort,d_edited=? WHERE id=$listId", array(time()));
}
jsonExit(array('total'=>1));
}
elseif(isset($_GET['publishList']))
{
check_write_access();
$listId = (int)_post('list');
$publish = (int)_post('publish');
$db->ex("UPDATE {$db->prefix}lists SET published=?,d_created=? WHERE id=$listId", array($publish ? 1 : 0, time()));
jsonExit(array('total'=>1));
}
elseif(isset($_GET['moveTask']))
{
check_write_access();
$id = (int)_post('id');
$fromId = (int)_post('from');
$toId = (int)_post('to');
$result = moveTask($id, $toId);
$t = array('total' => $result ? 1 : 0);
if($fromId == -1 && $result && $r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id")) {
$t['list'][] = prepareTaskRow($r);
}
jsonExit($t);
}
elseif(isset($_GET['changeListOrder']))
{
check_write_access();
$order = (array)_post('order');
$t = array();
$t['total'] = 0;
if($order)
{
$a = array();
$setCase = '';
foreach($order as $ow=>$id) {
$id = (int)$id;
$a[] = $id;
$setCase .= "WHEN id=$id THEN $ow\n";
}
$ids = implode(',', $a);
$db->dq("UPDATE {$db->prefix}lists SET d_edited=?, ow = CASE\n $setCase END WHERE id IN ($ids)",
array(time()) );
$t['total'] = 1;
}
jsonExit($t);
}
elseif(isset($_GET['parseTaskStr']))
{
check_write_access();
$t = array(
'title' => trim(_post('title')),
'prio' => 0,
'tags' => ''
);
if(Config::get('smartsyntax') != 0 && (false !== $a = parse_smartsyntax($t['title'])))
{
$t['title'] = $a['title'];
$t['prio'] = $a['prio'];
$t['tags'] = $a['tags'];
}
jsonExit($t);
}
elseif(isset($_GET['clearCompletedInList']))
{
check_write_access();
$t = array();
$t['total'] = 0;
$listId = (int)_post('list');
$db->ex("BEGIN");
$db->ex("DELETE FROM {$db->prefix}tag2task WHERE task_id IN (SELECT id FROM {$db->prefix}todolist WHERE list_id=? and compl=1)", array($listId));
$db->ex("DELETE FROM {$db->prefix}todolist WHERE list_id=$listId and compl=1");
$t['total'] = $db->affected();
$db->ex("COMMIT");
jsonExit($t);
}
elseif(isset($_GET['setShowNotesInList']))
{
check_write_access();
$listId = (int)_post('list');
$flag = (int)_post('shownotes');
$bitwise = ($flag == 0) ? 'taskview & ~2' : 'taskview | 2';
$db->dq("UPDATE {$db->prefix}lists SET taskview=$bitwise WHERE id=$listId");
jsonExit(array('total'=>1));
}
elseif(isset($_GET['setHideList']))
{
check_write_access();
$listId = (int)_post('list');
$flag = (int)_post('hide') ? 1 : 0;
if ($listId == -1) {
$opts = Config::requestDomain('alltasks.json');
$opts['hidden'] = $flag;
Config::saveDomain('alltasks.json', $opts);
}
else {
$bitwise = ($flag == 0) ? 'taskview & ~4' : 'taskview | 4';
$db->dq("UPDATE {$db->prefix}lists SET taskview=$bitwise WHERE id=$listId");
}
jsonExit(array('total'=>1));
}
elseif (isset($_POST['createSession']))
{
$t = array();
if (!need_auth()) {
$t['disabled'] = 1;
jsonExit($t);
}
if (access_token() == '') {
update_token();
}
$t['token'] = access_token();
$t['session'] = session_id();
jsonExit($t);
}
else {
jsonExit(['total' => 0]);
}
###################################################################################################
function prepareTaskRow($r)
{
$lang = Lang::instance();
$dueA = prepare_duedate($r['duedate']);
$formatCreatedInline = $formatCompletedInline = Config::get('dateformatshort');
if(date('Y') != date('Y',$r['d_created'])) $formatCreatedInline = Config::get('dateformat2');
if($r['d_completed'] && date('Y') != date('Y',$r['d_completed'])) $formatCompletedInline = Config::get('dateformat2');
$dCreated = timestampToDatetime($r['d_created']);
$dCompleted = $r['d_completed'] ? timestampToDatetime($r['d_completed']) : '';
return array(
'id' => $r['id'],
'title' => titleMarkup( $r['title'] ),
'titleText' => (string)$r['title'],
'listId' => $r['list_id'],
'date' => htmlarray($dCreated),
'dateInt' => (int)$r['d_created'],
'dateInline' => htmlarray(formatTime($formatCreatedInline, $r['d_created'])),
'dateInlineTitle' => htmlarray(sprintf($lang->get('taskdate_inline_created'), $dCreated)),
'dateEditedInt' => (int)$r['d_edited'],
'dateCompleted' => htmlarray($dCompleted),
'dateCompletedInline' => $r['d_completed'] ? htmlarray(formatTime($formatCompletedInline, $r['d_completed'])) : '',
'dateCompletedInlineTitle' => htmlarray(sprintf($lang->get('taskdate_inline_completed'), $dCompleted)),
'compl' => (int)$r['compl'],
'prio' => $r['prio'],
'note' => noteMarkup($r['note']),
'noteText' => (string)$r['note'],
'ow' => (int)$r['ow'],
'tags' => htmlarray($r['tags']),
'tags_ids' => htmlarray($r['tags_ids']),
'duedate' => $dueA['formatted'],
'dueClass' => $dueA['class'],
'dueStr' => htmlarray($r['compl'] && $dueA['timestamp'] ? formatTime($formatCompletedInline, $dueA['timestamp']) : $dueA['str']),
'dueInt' => date2int($r['duedate']),
'dueTitle' => htmlarray(sprintf($lang->get('taskdate_inline_duedate'), $dueA['formattedlong'])),
);
}
function check_read_access($listId = null)
{
check_token();
$db = DBConnection::instance();
if(is_logged()) return true;
if($listId !== null)
{
$id = $db->sq("SELECT id FROM {$db->prefix}lists WHERE id=? AND published=1", array($listId));
if($id) return;
}
jsonExit( array('total'=>0, 'list'=>array(), 'denied'=>1) );
}
function have_write_access($listId = null)
{
if(is_readonly()) return false;
// check list exist
if($listId !== null)
{
$db = DBConnection::instance();
$count = $db->sq("SELECT COUNT(*) FROM {$db->prefix}lists WHERE id=?", array($listId));
if(!$count) return false;
}
return true;
}
function check_write_access($listId = null)
{
check_token();
if(have_write_access($listId)) return;
jsonExit( array('total'=>0, 'list'=>array(), 'denied'=>1) );
}
/*
function inputTaskParams()
{
$a = array(
'id' => _post('id'),
'title'=> trim(_post('title')),
'note' => str_replace("\r\n", "\n", _post('note')),
'prio' => (int)_post('prio'),
'duedate' => '',
'tags' => trim(_post('tags')),
'listId' => (int)_post('list'),
);
if($a['prio'] < -1) $a['prio'] = -1;
elseif($a['prio'] > 2) $a['prio'] = 2;
return $a;
}
*/
function prepareTags($tagsStr)
{
$tags = explode(',', $tagsStr);
if(!$tags) return 0;
$aTags = array('tags'=>array(), 'ids'=>array());
foreach($tags as $tag)
{
$tag = str_replace(array('^','#'),'',trim($tag));
if($tag == '') continue;
$aTag = getOrCreateTag($tag);
if($aTag && !in_array($aTag['id'], $aTags['ids'])) {
$aTags['tags'][] = $aTag['name'];
$aTags['ids'][] = $aTag['id'];
}
}
return $aTags;
}
function getOrCreateTag($name)
{
$db = DBConnection::instance();
$tagId = $db->sq("SELECT id FROM {$db->prefix}tags WHERE name=?", array($name));
if($tagId) return array('id'=>$tagId, 'name'=>$name);
$db->ex("INSERT INTO {$db->prefix}tags (name) VALUES (?)", array($name));
return array('id'=>$db->lastInsertId(), 'name'=>$name);
}
function getTagId($tag)
{
$db = DBConnection::instance();
$id = $db->sq("SELECT id FROM {$db->prefix}tags WHERE name=?", array($tag));
return $id ? $id : 0;
}
function get_task_tags($id)
{
$db = DBConnection::instance();
$q = $db->dq("SELECT tag_id FROM {$db->prefix}tag2task WHERE task_id=?", $id);
$a = array();
while($r = $q->fetchRow()) {
$a[] = $r[0];
}
return $a;
}
function addTaskTags($taskId, $tagIds, $listId)
{
$db = DBConnection::instance();
if(!$tagIds) return;
foreach($tagIds as $tagId)
{
$db->ex("INSERT INTO {$db->prefix}tag2task (task_id,tag_id,list_id) VALUES (?,?,?)", array($taskId,$tagId,$listId));
}
}
function parse_smartsyntax($title)
{
$a = [
'prio' => 0,
'title' => $title,
'tags' => ''
];
if ( preg_match("|^([-+]{1}\d+)(.+)|", $a['title'], $m) ) {
$a['prio'] = (int) $m[1];
if ( $a['prio'] < -1 ) $a['prio'] = -1;
elseif ( $a['prio'] > 2 ) $a['prio'] = 2;
$a['title'] = trim($m[2]);
}
$tags = [];
$a['title'] = trim( preg_replace_callback(
"/(?:^|\s+)#([^#\s]+)/",
function ($matches) use (&$tags) {
$tags[] = $matches[1];
return '';
},
$a['title']
) );
if (count($tags) > 0) {
$a['tags'] = implode( ',' , $tags );
}
return $a;
}
function tag_size($qmin, $q, $step)
{
if($step == 0) return 1;
$v = ceil(($q - $qmin)/$step);
if($v == 0) return 0;
else return $v-1;
}
function parse_duedate($s)
{
$df2 = Config::get('dateformat2');
if(max((int)strpos($df2,'n'), (int)strpos($df2,'m')) > max((int)strpos($df2,'d'), (int)strpos($df2,'j'))) $formatDayFirst = true;
else $formatDayFirst = false;
$y = $m = $d = 0;
if(preg_match("|^(\d+)-(\d+)-(\d+)\b|", $s, $ma)) {
$y = (int)$ma[1]; $m = (int)$ma[2]; $d = (int)$ma[3];
}
elseif(preg_match("|^(\d+)\/(\d+)\/(\d+)\b|", $s, $ma))
{
if($formatDayFirst) {
$d = (int)$ma[1]; $m = (int)$ma[2]; $y = (int)$ma[3];
} else {
$m = (int)$ma[1]; $d = (int)$ma[2]; $y = (int)$ma[3];
}
}
elseif(preg_match("|^(\d+)\.(\d+)\.(\d+)\b|", $s, $ma)) {
$d = (int)$ma[1]; $m = (int)$ma[2]; $y = (int)$ma[3];
}
elseif(preg_match("|^(\d+)\.(\d+)\b|", $s, $ma)) {
$d = (int)$ma[1]; $m = (int)$ma[2];
$a = explode(',', date('Y,m,d'));
if( $m<(int)$a[1] || ($m==(int)$a[1] && $d<(int)$a[2]) ) $y = (int)$a[0]+1;
else $y = (int)$a[0];
}
elseif(preg_match("|^(\d+)\/(\d+)\b|", $s, $ma))
{
if($formatDayFirst) {
$d = (int)$ma[1]; $m = (int)$ma[2];
} else {
$m = (int)$ma[1]; $d = (int)$ma[2];
}
$a = explode(',', date('Y,m,d'));
if( $m<(int)$a[1] || ($m==(int)$a[1] && $d<(int)$a[2]) ) $y = (int)$a[0]+1;
else $y = (int)$a[0];
}
else return null;
if($y < 100) $y = 2000 + $y;
elseif($y < 1000 || $y > 2099) $y = 2000 + (int)substr((string)$y, -2);
if($m > 12) $m = 12;
$maxdays = daysInMonth($m,$y);
if($m < 10) $m = '0'.$m;
if($d > $maxdays) $d = $maxdays;
elseif($d < 10) $d = '0'.$d;
return "$y-$m-$d";
}
function prepare_duedate($duedate)
{
$lang = Lang::instance();
$a = array( 'class'=>'', 'str'=>'', 'formatted'=>'', 'formattedlong'=>'', 'timestamp'=>0 );
if($duedate == '') {
return $a;
}
$ad = explode('-', $duedate);
$at = explode('-', date('Y-m-d'));
$a['timestamp'] = mktime(0,0,0,$ad[1],$ad[2],$ad[0]);
$diff = mktime(0,0,0,$ad[1],$ad[2],$ad[0]) - mktime(0,0,0,$at[1],$at[2],$at[0]);
if($diff < -604800 && $ad[0] == $at[0]) { $a['class'] = 'past'; $a['str'] = formatDate3(Config::get('dateformatshort'), (int)$ad[0], (int)$ad[1], (int)$ad[2], $lang); }
elseif($diff < -604800) { $a['class'] = 'past'; $a['str'] = formatDate3(Config::get('dateformat2'), (int)$ad[0], (int)$ad[1], (int)$ad[2], $lang); }
elseif($diff < -86400) { $a['class'] = 'past'; $a['str'] = sprintf($lang->get('daysago'),ceil(abs($diff)/86400)); }
elseif($diff < 0) { $a['class'] = 'past'; $a['str'] = $lang->get('yesterday'); }
elseif($diff < 86400) { $a['class'] = 'today'; $a['str'] = $lang->get('today'); }
elseif($diff < 172800) { $a['class'] = 'today'; $a['str'] = $lang->get('tomorrow'); }
elseif($diff < 691200) { $a['class'] = 'soon'; $a['str'] = sprintf($lang->get('indays'),ceil($diff/86400)); }
elseif($ad[0] == $at[0]) { $a['class'] = 'future'; $a['str'] = formatDate3(Config::get('dateformatshort'), (int)$ad[0], (int)$ad[1], (int)$ad[2], $lang); }
else { $a['class'] = 'future'; $a['str'] = formatDate3(Config::get('dateformat2'), (int)$ad[0], (int)$ad[1], (int)$ad[2], $lang); }
#avoid short year
$fmt = str_replace('y', 'Y', Config::get('dateformat2'));
$a['formatted'] = formatTime($fmt, $a['timestamp']);
$a['formattedlong'] = formatTime(Config::get('dateformat'), $a['timestamp']);
return $a;
}
function date2int($d)
{
if(!$d) return 33330000;
$ad = explode('-', $d);
$s = $ad[0];
if(strlen($ad[1]) < 2) $s .= "0$ad[1]"; else $s .= $ad[1];
if(strlen($ad[2]) < 2) $s .= "0$ad[2]"; else $s .= $ad[2];
return (int)$s;
}
function daysInMonth($m, $y=0)
{
if($y == 0) $y = (int)date('Y');
$a = array(1=>31,(($y-2000)%4?28:29),31,30,31,30,31,31,30,31,30,31);
if(isset($a[$m])) return $a[$m]; else return 0;
}
function myErrorHandler($errno, $errstr, $errfile, $errline)
{
if ($errno==E_ERROR || $errno==E_CORE_ERROR || $errno==E_COMPILE_ERROR || $errno==E_USER_ERROR || $errno==E_PARSE) {
$error = 'Error';
}
elseif ($errno==E_WARNING || $errno==E_CORE_WARNING || $errno==E_COMPILE_WARNING || $errno==E_USER_WARNING || $errno==E_STRICT) {
if (error_reporting() & $errno) $error = 'Warning'; else return;
}
elseif ($errno==E_NOTICE || $errno==E_USER_NOTICE || $errno==E_DEPRECATED || $errno==E_USER_DEPRECATED) {
if (error_reporting() & $errno) $error = 'Notice'; else return;
}
else $error = "Error ($errno)"; # here may be E_RECOVERABLE_ERROR
throw new Exception("$error: '$errstr' in $errfile:$errline", -1);
}
function myExceptionHandler($e)
{
// to avoid Exception thrown without a stack frame
try
{
if (-1 == $e->getCode()) {
//thrown in myErrorHandler
logAndDie( $e->getMessage() );
}
$c = get_class($e);
$errText = "Exception ($c): '". $e->getMessage(). "' in ". $e->getFile(). ":". $e->getLine() ;
if (MTT_DEBUG) {
if ( count($e->getTrace()) > 0 ) {
$errText .= "\n". $e->getTraceAsString() ;
}
}
logAndDie($errText);
}
catch (Exception $e) {
logAndDie('Exception in ExceptionHandler: \''. $e->getMessage() .'\' in '. $e->getFile() .':'. $e->getLine());
}
exit;
}
function deleteTask($id)
{
check_write_access();
$db = DBConnection::instance();
$db->ex("BEGIN");
$db->ex("DELETE FROM {$db->prefix}tag2task WHERE task_id=$id");
//TODO: delete unused tags?
$db->dq("DELETE FROM {$db->prefix}todolist WHERE id=$id");
$affected = $db->affected();
$db->ex("COMMIT");
return $affected;
}
function moveTask($id, $listId)
{
check_write_access();
$db = DBConnection::instance();
// Check task exists and not in target list
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=?", array($id));
if(!$r || $listId == $r['list_id']) return false;
// Check target list exists
if(!$db->sq("SELECT COUNT(*) FROM {$db->prefix}lists WHERE id=?", $listId))
return false;
$ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}todolist WHERE list_id=? AND compl=?", array($listId, $r['compl']?1:0));
$db->ex("BEGIN");
$db->ex("UPDATE {$db->prefix}tag2task SET list_id=? WHERE task_id=?", array($listId, $id));
$db->dq("UPDATE {$db->prefix}todolist SET list_id=?, ow=?, d_edited=? WHERE id=?", array($listId, $ow, time(), $id));
$db->ex("COMMIT");
return true;
}
function prepareList($row)
{
$taskview = (int)$row['taskview'];
return array(
'id' => $row['id'],
'name' => htmlarray($row['name']),
'sort' => (int)$row['sorting'],
'published' => $row['published'] ? 1 :0,
'showCompl' => $taskview & 1 ? 1 : 0,
'showNotes' => $taskview & 2 ? 1 : 0,
'hidden' => $taskview & 4 ? 1 : 0,
);
}
function prepareAllTasksList()
{
//default values
$hidden = 1;
$sort = 3;
$opts = Config::requestDomain('alltasks.json');
if ( isset($opts['hidden']) ) $hidden = (int)$opts['hidden'] ? 1 : 0;
if ( isset($opts['sort']) ) $sort = (int)$opts['sort'];
return array(
'id' => -1,
'name' => htmlarray(__('alltasks')),
'sort' => $sort,
'published' => 0,
'showCompl' => 1,
'showNotes' => 0,
'hidden' => $hidden,
);
}
function getUserListsSimple()
{
$db = DBConnection::instance();
$a = array();
$q = $db->dq("SELECT id,name FROM {$db->prefix}lists ORDER BY id ASC");
while($r = $q->fetchRow()) {
$a[$r[0]] = $r[1];
}
return $a;
}

227
src/api.php Normal file
View file

@ -0,0 +1,227 @@
<?php declare(strict_types=1);
/*
This file is a part of myTinyTodo.
(C) Copyright 2022 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
require_once('./init.php');
if (MTT_DEBUG) {
set_error_handler('myErrorHandler'); //catch Notices, Warnings
set_exception_handler('myExceptionHandler');
}
else {
ini_set('display_errors', '0');
}
require_once(MTTINC. 'api/ListsController.php');
require_once(MTTINC. 'api/TasksController.php');
require_once(MTTINC. 'api/TagsController.php');
require_once(MTTINC. 'api/AuthController.php');
$req = new ApiRequest();
$endpoints = array(
'/lists' => [
'GET' => [ ListsController::class , 'get' ],
'POST' => [ ListsController::class , 'post' ],
'PUT' => [ ListsController::class , 'put' ],
],
'/lists/(-?\d+)' => [
'GET' => [ ListsController::class , 'getId' ],
'PUT' => [ ListsController::class , 'putId' ],
'DELETE' => [ ListsController::class , 'deleteId' ],
],
'/tasks' => [
'GET' => [ TasksController::class , 'get' ],
'POST' => [ TasksController::class , 'post' ],
'PUT' => [ TasksController::class , 'put' ],
],
'/tasks/(-?\d+)' => [
'PUT' => [ TasksController::class , 'putId' ],
'DELETE' => [ TasksController::class , 'deleteId' ],
],
'/tasks/parseTitle' => [
'POST' => [ TasksController::class , 'postTitleParse' ],
],
'/tagCloud/(-?\d+)' => [
'GET' => [ TagsController::class , 'getCloud' ],
],
'/suggestTags' => [
'GET' => [ TagsController::class , 'getSuggestions' ],
],
'/(login|logout|session)' => [
'POST' => [ AuthController::class , 'postAction' ],
],
);
$executed = false;
$data = null;
foreach ($endpoints as $search => $methods) {
$m = array();
if (preg_match("#^$search$#", $req->path, $m)) {
$classDescr = $methods[$req->method] ?? null;
// check if http method is supported for path
if ( is_null($classDescr) ) {
http_response_code(500);
die ("Unknown method for resource");
}
if ( !is_array($classDescr) || count($classDescr) != 2) {
http_response_code(500);
die ("Incorrect method definition");
}
// check if class method exists
$class = $classDescr[0];
$classMethod = $classDescr[1];
$param = null;
if (count($m) >= 2) {
$param = $m[1];
}
if (method_exists($class, $classMethod)) { // test for static with ReflectionMethod?
if ($req->method != 'GET' && $req->contentType == 'application/json') {
if ($req->decodeJsonBody() === false) {
http_response_code(500);
die ("Failed to parse JSON body");
}
}
$instance = new $class($req);
$data = $instance->$classMethod($param);
$executed = true;
break;
}
else {
http_response_code(405);
if (MTT_DEBUG) {
die ("Class method $class:$classMethod() not found");
}
die ("Class method not found");
}
}
}
if ($executed) {
if (is_null($data)) {
http_response_code(404);
}
jsonExit($data);
}
else {
http_response_code(404);
die ("Unknown command");
}
function myErrorHandler($errno, $errstr, $errfile, $errline)
{
if ($errno==E_ERROR || $errno==E_CORE_ERROR || $errno==E_COMPILE_ERROR || $errno==E_USER_ERROR || $errno==E_PARSE) {
$error = 'Error';
}
elseif ($errno==E_WARNING || $errno==E_CORE_WARNING || $errno==E_COMPILE_WARNING || $errno==E_USER_WARNING || $errno==E_STRICT) {
if (error_reporting() & $errno) $error = 'Warning'; else return;
}
elseif ($errno==E_NOTICE || $errno==E_USER_NOTICE || $errno==E_DEPRECATED || $errno==E_USER_DEPRECATED) {
if (error_reporting() & $errno) $error = 'Notice'; else return;
}
else $error = "Error ($errno)"; # here may be E_RECOVERABLE_ERROR
throw new Exception("$error: '$errstr' in $errfile:$errline", -1);
}
function myExceptionHandler(Throwable $e)
{
// to avoid Exception thrown without a stack frame
try
{
if (-1 == $e->getCode()) {
//thrown in myErrorHandler
http_response_code(500);
logAndDie( $e->getMessage() );
}
$c = get_class($e);
$errText = "Exception ($c): '". $e->getMessage(). "' in ". $e->getFile(). ":". $e->getLine() ;
if (MTT_DEBUG) {
if ( count($e->getTrace()) > 0 ) {
$errText .= "\n". $e->getTraceAsString() ;
}
}
http_response_code(500);
logAndDie($errText);
}
catch (Exception $e) {
http_response_code(500);
logAndDie('Exception in ExceptionHandler: \''. $e->getMessage() .'\' in '. $e->getFile() .':'. $e->getLine());
}
exit;
}
function checkReadAccess(?int $listId = null)
{
check_token();
$db = DBConnection::instance();
if (is_logged()) return true;
if ($listId !== null)
{
$id = $db->sq("SELECT id FROM {$db->prefix}lists WHERE id=? AND published=1", array($listId));
if ($id) return;
}
jsonExit( array('total'=>0, 'list'=>array(), 'denied'=>1) );
}
function checkWriteAccess(?int $listId = null)
{
check_token();
if (haveWriteAccess($listId)) return;
http_response_code(403);
jsonExit( array('total'=>0, 'list'=>array(), 'denied'=>1) );
}
function haveWriteAccess(?int $listId = null)
{
if (is_readonly()) {
return false;
}
// check list exist
if ($listId !== null)
{
$db = DBConnection::instance();
$count = $db->sq("SELECT COUNT(*) FROM {$db->prefix}lists WHERE id=?", array($listId));
if (!$count) return false;
}
return true;
}
class ApiRequest
{
public $path;
public $method;
public $contentType;
public $jsonBody;
function __construct() {
$this->path = $_SERVER['PATH_INFO'] ?? '';
$this->method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
$this->contentType = $_SERVER['CONTENT_TYPE'] ?? '';
}
function decodeJsonBody() {
$this->jsonBody = json_decode( file_get_contents('php://input'), true, 10, JSON_INVALID_UTF8_SUBSTITUTE );
return $this->jsonBody;
}
}
abstract class ApiController
{
/**
* @var ApiRequest
*/
protected $req;
function __construct(ApiRequest $req) {
$this->req = $req;
}
}

View file

@ -26,11 +26,11 @@
<script type="text/javascript" src="<?php mttinfo('includes_url'); ?>jquery/jquery-ui-1.13.1.min.js"></script>
<script type="text/javascript" src="<?php mttinfo('includes_url'); ?>jquery/jquery.ui.touch-punch-1.0.8.js"></script>
<script type="text/javascript" src="<?php mttinfo('includes_url'); ?>mytinytodo.js?v=<?php mttinfo('version'); ?>"></script>
<script type="text/javascript" src="<?php mttinfo('includes_url'); ?>mytinytodo_ajax_storage.js?v=<?php mttinfo('version'); ?>"></script>
<script type="text/javascript" src="<?php mttinfo('includes_url'); ?>mytinytodo_api.js?v=<?php mttinfo('version'); ?>"></script>
<script type="text/javascript">
$().ready(function(){
mytinytodo.setApi(mytinytodoStorageAjax).init(<?php js_options(); ?>).run();
mytinytodo.setApiDriver(MytinytodoAjaxApi).init(<?php js_options(); ?>).run();
});
</script>

View file

@ -0,0 +1,63 @@
<?php declare(strict_types=1);
/*
This file is a part of myTinyTodo.
(C) Copyright 2022 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
class AuthController extends ApiController {
function postAction($action)
{
switch ($action) {
case 'login': return $this->login(); break;
case 'logout': return $this->logout(); break;
case 'session': return $this->createSession(); break;
default: return ['total' => 0]; // error 400 ?
}
}
private function login()
{
check_token();
$t = array('logged' => 0);
if (!need_auth()) {
$t['disabled'] = 1;
return $t;
}
$password = $this->req->jsonBody['password'] ?? '';
if ( isPasswordEqualsToHash($password, Config::get('password')) ) {
updateSessionLogged(true);
$t['token'] = update_token();
$t['logged'] = 1;
}
return $t;
}
private function logout()
{
check_token();
updateSessionLogged(false);
update_token();
session_regenerate_id(true);
$t = array('logged' => 0);
return $t;
}
private function createSession()
{
$t = array();
if (!need_auth()) {
$t['disabled'] = 1;
return $t;
}
if (access_token() == '') {
update_token();
}
$t['token'] = access_token();
$t['session'] = session_id();
return $t;
}
}

View file

@ -0,0 +1,293 @@
<?php declare(strict_types=1);
/*
This file is a part of myTinyTodo.
(C) Copyright 2022 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
class ListsController extends ApiController {
/**
* Get all lists
* @return array
* @throws Exception
*/
function get()
{
$db = DBConnection::instance();
check_token();
$t = array();
$t['total'] = 0;
if (!is_logged()) {
$sqlWhere = 'WHERE published=1';
}
else {
$sqlWhere = '';
$t['list'][] = $this->prepareAllTasksList(); // show alltasks lists only for authorized user
$t['total'] = 1;
}
$q = $db->dq("SELECT * FROM {$db->prefix}lists $sqlWhere ORDER BY ow ASC, id ASC");
while ($r = $q->fetchAssoc())
{
$t['total']++;
$t['list'][] = $this->prepareList($r);
}
return $t;
}
/**
* Create new list
* Code 201 on success
* @return array
* @throws Exception
*/
function post()
{
checkWriteAccess();
$db = DBConnection::instance();
$t = array();
$t['total'] = 0;
$name = str_replace(
array('"',"'",'<','>','&'),
'',
trim( $this->req->jsonBody['name'] ?? '' )
);
$ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}lists");
$db->dq("INSERT INTO {$db->prefix}lists (uuid,name,ow,d_created,d_edited,taskview) VALUES (?,?,?,?,?,?)",
array(generateUUID(), $name, $ow, time(), time(), 1) );
$id = $db->lastInsertId();
$t['total'] = 1;
$r = $db->sqa("SELECT * FROM {$db->prefix}lists WHERE id=$id");
$t['list'][] = $this->prepareList($r);
return $t;
}
/**
* Actions with all lists
* @return array
* @throws Exception
*/
function put()
{
checkWriteAccess();
$action = $this->req->jsonBody['action'] ?? '';
switch ($action) {
case 'order': return $this->changeListOrder(); break;
default: return ['total' => 0]; // error 400 ?
}
}
/* Single list */
/**
* Get single list by Id
* @param mixed $id
* @return null|array
* @throws Exception
*/
function getId($id)
{
checkReadAccess($id);
$db = DBConnection::instance();
$r = $db->sqa( "SELECT * FROM {$db->prefix}lists WHERE id=?", array($id) );
if (!$r) {
return null;
}
$t = $this->prepareList($r);
return $t;
}
/**
* Delete list by Id
* @param mixed $id
* @return array
* @throws Exception
*/
function deleteId($id)
{
checkWriteAccess();
$db = DBConnection::instance();
$t = array();
$t['total'] = 0;
$id = (int)$id;
$db->ex("BEGIN");
$db->ex("DELETE FROM {$db->prefix}lists WHERE id=$id");
$t['total'] = $db->affected();
if ($t['total']) {
$db->ex("DELETE FROM {$db->prefix}tag2task WHERE list_id=$id");
$db->ex("DELETE FROM {$db->prefix}todolist WHERE list_id=$id");
}
$db->ex("COMMIT");
return $t;
}
/**
* Edit some properties of List
* Actions: rename
* @param mixed $id
* @return array
* @throws Exception
*/
function putId($id)
{
checkWriteAccess();
$id = (int)$id;
$action = $this->req->jsonBody['action'] ?? '';
switch ($action) {
case 'rename': return $this->renameList($id); break;
case 'sort': return $this->sortList($id); break;
case 'publish': return $this->publishList($id); break;
case 'showNotes': return $this->showNotes($id); break;
case 'hide': return $this->hideList($id); break;
case 'clearCompleted': return $this->clearCompleted($id); break;
default: return ['total' => 0];
}
}
/* Private Functions */
private function prepareAllTasksList()
{
//default values
$hidden = 1;
$sort = 3;
$opts = Config::requestDomain('alltasks.json');
if ( isset($opts['hidden']) ) $hidden = (int)$opts['hidden'] ? 1 : 0;
if ( isset($opts['sort']) ) $sort = (int)$opts['sort'];
return array(
'id' => -1,
'name' => htmlarray(__('alltasks')),
'sort' => $sort,
'published' => 0,
'showCompl' => 1,
'showNotes' => 0,
'hidden' => $hidden,
);
}
private function prepareList($row)
{
$taskview = (int)$row['taskview'];
return array(
'id' => $row['id'],
'name' => htmlarray($row['name']),
'sort' => (int)$row['sorting'],
'published' => $row['published'] ? 1 :0,
'showCompl' => $taskview & 1 ? 1 : 0,
'showNotes' => $taskview & 2 ? 1 : 0,
'hidden' => $taskview & 4 ? 1 : 0,
);
}
private function renameList(int $id)
{
$db = DBConnection::instance();
$t = array();
$t['total'] = 0;
$name = str_replace(
array('"',"'",'<','>','&'),
array('','','','',''),
trim($this->req->jsonBody['name'] ?? '')
);
$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);
return $t;
}
private function sortList(int $listId)
{
$db = DBConnection::instance();
$sort = (int)($this->req->jsonBody['sort'] ?? 0);
if ($sort < 0 || $sort > 104) $sort = 0;
elseif ($sort < 101 && $sort > 4) $sort = 0;
if ($listId == -1) {
$opts = Config::requestDomain('alltasks.json');
$opts['sort'] = $sort;
Config::saveDomain('alltasks.json', $opts);
}
else {
$db->ex("UPDATE {$db->prefix}lists SET sorting=$sort,d_edited=? WHERE id=$listId", array(time()));
}
return ['total'=>1];
}
private function publishList(int $listId)
{
$db = DBConnection::instance();
$publish = (int)($this->req->jsonBody['publish'] ?? 0);
$db->ex("UPDATE {$db->prefix}lists SET published=?,d_created=? WHERE id=$listId", array($publish ? 1 : 0, time()));
return ['total'=>1];
}
private function showNotes(int $listId)
{
$db = DBConnection::instance();
$flag = (int)($this->req->jsonBody['shownotes'] ?? 0);
$bitwise = ($flag == 0) ? 'taskview & ~2' : 'taskview | 2';
$db->dq("UPDATE {$db->prefix}lists SET taskview=$bitwise WHERE id=$listId");
return ['total'=>1];
}
private function hideList(int $listId)
{
$db = DBConnection::instance();
$flag = (int)($this->req->jsonBody['hide'] ?? 0);
if ($listId == -1) {
$opts = Config::requestDomain('alltasks.json');
$opts['hidden'] = $flag ? 1 : 0;
Config::saveDomain('alltasks.json', $opts);
}
else {
$bitwise = ($flag == 0) ? 'taskview & ~4' : 'taskview | 4';
$db->dq("UPDATE {$db->prefix}lists SET taskview=$bitwise WHERE id=$listId");
}
return ['total'=>1];
}
private function clearCompleted(int $listId)
{
$db = DBConnection::instance();
$t = array();
$t['total'] = 0;
$db->ex("BEGIN");
$db->ex("DELETE FROM {$db->prefix}tag2task WHERE task_id IN (SELECT id FROM {$db->prefix}todolist WHERE list_id=? and compl=1)", array($listId));
$db->ex("DELETE FROM {$db->prefix}todolist WHERE list_id=$listId and compl=1");
$t['total'] = $db->affected();
$db->ex("COMMIT");
return $t;
}
private function changeListOrder()
{
$t = array();
$t['total'] = 0;
if (!is_array($this->req->jsonBody['order'])) {
return $t;
}
$db = DBConnection::instance();
$order = $this->req->jsonBody['order'];
$a = array();
$setCase = '';
foreach ($order as $ow => $id) {
$id = (int)$id;
$a[] = $id;
$setCase .= "WHEN id=$id THEN $ow\n";
}
$ids = implode(',', $a);
$db->dq("UPDATE {$db->prefix}lists SET d_edited=?, ow = CASE\n $setCase END WHERE id IN ($ids)",
array(time()) );
$t['total'] = 1;
return $t;
}
}

View file

@ -0,0 +1,83 @@
<?php declare(strict_types=1);
/*
This file is a part of myTinyTodo.
(C) Copyright 2022 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
class TagsController extends ApiController {
/**
* Get tag cloud
* @return array
* @throws Exception
*/
function getCloud($listId)
{
$listId = (int)$listId;
checkReadAccess($listId);
$db = DBConnection::instance();
$q = $db->dq("SELECT name,tag_id,COUNT(tag_id) AS tags_count FROM {$db->prefix}tag2task INNER JOIN {$db->prefix}tags ON tag_id=id ".
"WHERE list_id=$listId GROUP BY (tag_id) ORDER BY tags_count ASC");
$at = array();
$ac = array();
while ($r = $q->fetchAssoc()) {
$at[] = array(
'name' => $r['name'],
'id' => $r['tag_id']
);
$ac[] = (int) $r['tags_count'];
}
$t = array();
$t['total'] = 0;
$count = sizeof($at);
if (!$count) {
return $t;
}
$qmax = max($ac);
$qmin = min($ac);
if ($count >= 10) $grades = 10;
else $grades = $count;
$step = ($qmax - $qmin)/$grades;
foreach ($at as $i => $tag)
{
$t['cloud'][] = array(
'tag' => htmlspecialchars($tag['name']),
'id' => (int)$tag['id'],
'w' => $this->tagWeight($qmin, $ac[$i], $step)
);
}
$t['total'] = $count;
return $t;
}
function getSuggestions($listId)
{
$listId = (int)_get('list');
checkWriteAccess($listId);
$db = DBConnection::instance();
$begin = trim(_get('q'));
$limit = 8;
$q = $db->dq("SELECT name,id FROM {$db->prefix}tags INNER JOIN {$db->prefix}tag2task ON id=tag_id WHERE list_id=$listId AND name LIKE ".
$db->quoteForLike('%s%%',$begin) ." GROUP BY tag_id ORDER BY name LIMIT $limit");
$t = array();
while ($r = $q->fetchRow()) {
$t[] = $r[0];
}
return $t;
}
private function tagWeight(int $qmin, int $q, float $step)
{
if ($step == 0) return 1;
$v = ceil(($q - $qmin)/$step);
if ($v == 0) return 0;
else return $v-1;
}
}

View file

@ -0,0 +1,662 @@
<?php declare(strict_types=1);
/*
This file is a part of myTinyTodo.
(C) Copyright 2022 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
require_once(MTTINC. 'markup.php');
class TasksController extends ApiController {
/**
* Get tasks.
* Filters are set with query parameters.
* @return array
* @throws Exception
*/
function get()
{
$listId = (int)_get('list');
checkReadAccess($listId);
$db = DBConnection::instance();
$sqlWhere = $inner = $sqlWhereListId = $sqlInnerWhereListId = '';
if ($listId == -1) {
$userLists = $this->getUserListsSimple();
$sqlWhereListId = "{$db->prefix}todolist.list_id IN (". implode(',', array_keys($userLists)). ") ";
$sqlInnerWhereListId = "list_id IN (". implode(',', array_keys($userLists)). ") ";
}
else {
$sqlWhereListId = "{$db->prefix}todolist.list_id=". $listId;
$sqlInnerWhereListId = "list_id=$listId ";
}
if (_get('compl') == 0) {
$sqlWhere .= ' AND compl=0';
}
$tag = trim(_get('t'));
if ($tag != '')
{
$at = explode(',', $tag);
$tagIds = array();
$tagExIds = array();
foreach ($at as $i=>$atv) {
$atv = trim($atv);
if ($atv == '' || $atv == '^') continue;
if (substr($atv,0,1) == '^') {
$tagExIds[] = $this->getTagId(substr($atv,1));
} else {
$tagIds[] = $this->getTagId($atv);
}
}
// Include tags: All
if (sizeof($tagIds) > 1) {
$inner .= "INNER JOIN (SELECT task_id, COUNT(tag_id) AS c FROM {$db->prefix}tag2task WHERE $sqlInnerWhereListId AND tag_id IN (".
implode(',',$tagIds). ") GROUP BY task_id) AS t2t ON id=t2t.task_id";
$sqlWhere .= " AND c=". sizeof($tagIds);
}
elseif ($tagIds) {
$inner .= "INNER JOIN {$db->prefix}tag2task ON id=task_id";
$sqlWhere .= " AND tag_id = {$tagIds[0]}";
}
// Exclude tags
if (sizeof($tagExIds) > 0) {
$sqlWhere .= " AND {$db->prefix}todolist.id NOT IN (SELECT DISTINCT task_id FROM {$db->prefix}tag2task WHERE $sqlInnerWhereListId AND tag_id IN (".
implode(',',$tagExIds). "))";
}
//no optimization for single exTag
}
$s = trim(_get('s'));
if ($s != '') {
if (preg_match("|^#(\d+)$|", $s, $m)) $sqlWhere .= " AND {$db->prefix}todolist.id = ". (int)$m[1];
else $sqlWhere .= " AND (title LIKE ". $db->quoteForLike("%%%s%%",$s). " OR note LIKE ". $db->quoteForLike("%%%s%%",$s). ")";
}
$sort = (int)_get('sort');
$sqlSort = "ORDER BY compl ASC, ";
if ($sort == 1) $sqlSort .= "prio DESC, ddn ASC, duedate ASC, ow ASC"; // byPrio
elseif ($sort == 101) $sqlSort .= "prio ASC, ddn DESC, duedate DESC, ow DESC"; // byPrio (reverse)
elseif ($sort == 2) $sqlSort .= "ddn ASC, duedate ASC, prio DESC, ow ASC"; // byDueDate
elseif ($sort == 102) $sqlSort .= "ddn DESC, duedate DESC, prio ASC, ow DESC"; // byDueDate (reverse)
elseif ($sort == 3) $sqlSort .= "d_created ASC, prio DESC, ow ASC"; // byDateCreated
elseif ($sort == 103) $sqlSort .= "d_created DESC, prio ASC, ow DESC"; // byDateCreated (reverse)
elseif ($sort == 4) $sqlSort .= "d_edited ASC, prio DESC, ow ASC"; // byDateModified
elseif ($sort == 104) $sqlSort .= "d_edited DESC, prio ASC, ow DESC"; // byDateModified (reverse)
else $sqlSort .= "ow ASC";
$t = array();
$t['total'] = 0;
$t['list'] = array();
$q = $db->dq("SELECT *, duedate IS NULL AS ddn FROM {$db->prefix}todolist $inner WHERE $sqlWhereListId $sqlWhere $sqlSort");
while ($r = $q->fetchAssoc())
{
$t['total']++;
$t['list'][] = $this->prepareTaskRow($r);
}
if (_get('setCompl') && haveWriteAccess($listId)) {
$bitwise = (_get('compl') == 0) ? 'taskview & ~1' : 'taskview | 1';
$db->dq("UPDATE {$db->prefix}lists SET taskview=$bitwise WHERE id=$listId");
}
return $t;
}
/**
* Create new task
* action: simple or full
* @return mixed
* @throws Exception
*/
function post()
{
$listId = (int)($this->req->jsonBody['list'] ?? 0);
checkWriteAccess($listId);
$action = $this->req->jsonBody['action'] ?? '';
if ($action == 'full') {
return $this->fullNewTaskInList($listId);
}
return $this->newTaskInList($listId);
}
/**
* Actions with multiple tasks
* @return array
* @throws Exception
*/
function put()
{
checkWriteAccess();
$action = $this->req->jsonBody['action'] ?? '';
switch ($action) {
case 'order': return $this->changeTaskOrder(); break;
default: return ['total' => 0]; // error 400 ?
}
}
/**
* Delete task by Id
* @param mixed $id
* @return array
* @throws Exception
*/
function deleteId($id)
{
checkWriteAccess();
$id = (int)$id;
$db = DBConnection::instance();
$db->ex("BEGIN");
$db->ex("DELETE FROM {$db->prefix}tag2task WHERE task_id=$id");
//TODO: delete unused tags?
$db->dq("DELETE FROM {$db->prefix}todolist WHERE id=$id");
$deleted = $db->affected();
$db->ex("COMMIT");
$t = array();
$t['total'] = $deleted;
$t['list'][] = array('id' => $id);
return $t;
}
/**
* Edit some properties of Task
* @param mixed $id
* @return array
* @throws Exception
*/
function putId($id)
{
checkWriteAccess();
$id = (int)$id;
$action = $this->req->jsonBody['action'] ?? '';
switch ($action) {
case 'edit': return $this->editTask($id); break;
case 'complete': return $this->completeTask($id); break;
case 'note': return $this->editNote($id); break;
case 'move': return $this->moveTask($id); break;
case 'priority': return $this->priorityTask($id); break;
default: return ['total' => 0];
}
}
/**
* Parse task input string to components for representing in edit/add form
* @return array
* @throws Exception
*/
function postTitleParse()
{
checkWriteAccess();
$t = array(
'title' => trim( $this->req->jsonBody['title'] ?? '' ),
'prio' => 0,
'tags' => ''
);
if (Config::get('smartsyntax') != 0 && (false !== $a = $this->parseSmartSyntax($t['title'])))
{
$t['title'] = $a['title'];
$t['prio'] = $a['prio'];
$t['tags'] = $a['tags'];
}
return $t;
}
/* Private Functions */
private function newTaskInList(int $listId)
{
$db = DBConnection::instance();
$t = array();
$t['total'] = 0;
$title = trim($this->req->jsonBody['title'] ?? '');
$prio = 0;
$tags = '';
if (Config::get('smartsyntax') != 0)
{
$a = $this->parseSmartSyntax($title);
if ($a === false) {
return $t;
}
$title = $a['title'];
$prio = $a['prio'];
$tags = $a['tags'];
}
if ($title == '') {
return $t;
}
if (Config::get('autotag')) {
$tags .= ',' . ($this->req->jsonBody['tag'] ?? '');
}
$ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}todolist WHERE list_id=$listId AND compl=0");
$db->ex("BEGIN");
$db->dq("INSERT INTO {$db->prefix}todolist (uuid,list_id,title,d_created,d_edited,ow,prio) VALUES (?,?,?,?,?,?,?)",
array(generateUUID(), $listId, $title, time(), time(), $ow, $prio) );
$id = (int) $db->lastInsertId();
if ($tags != '')
{
$aTags = $this->prepareTags($tags);
if ($aTags) {
$this->addTaskTags($id, $aTags['ids'], $listId);
$db->ex("UPDATE {$db->prefix}todolist SET tags=?,tags_ids=? WHERE id=$id", array(implode(',',$aTags['tags']), implode(',',$aTags['ids'])));
}
}
$db->ex("COMMIT");
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
$t['list'][] = $this->prepareTaskRow($r);
$t['total'] = 1;
return $t;
}
private function fullNewTaskInList(int $listId)
{
$db = DBConnection::instance();
$title = trim($this->req->jsonBody['title'] ?? '');
$note = str_replace("\r\n", "\n", $this->req->jsonBody['note'] ?? '');
$prio = (int)($this->req->jsonBody['prio'] ?? 0);
if ($prio < -1) $prio = -1;
elseif ($prio > 2) $prio = 2;
$duedate = $this->parseDuedate(trim( $this->req->jsonBody['duedate'] ?? '' ));
$t = array();
$t['total'] = 0;
if ($title == '') {
return $t;
}
$tags = $this->req->jsonBody['tags'] ?? '';
if (Config::get('autotag'))
$tags .= ',' . ($this->req->jsonBody['tag'] ?? '');
$ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}todolist WHERE list_id=$listId AND compl=0");
$db->ex("BEGIN");
$db->dq("INSERT INTO {$db->prefix}todolist (uuid,list_id,title,d_created,d_edited,ow,prio,note,duedate) VALUES (?,?,?,?,?,?,?,?,?)",
array(generateUUID(), $listId, $title, time(), time(), $ow, $prio, $note, $duedate) );
$id = (int) $db->lastInsertId();
if ($tags != '')
{
$aTags = $this->prepareTags($tags);
if ($aTags) {
$this->addTaskTags($id, $aTags['ids'], $listId);
$db->ex("UPDATE {$db->prefix}todolist SET tags=?,tags_ids=? WHERE id=$id", array(implode(',',$aTags['tags']), implode(',',$aTags['ids'])));
}
}
$db->ex("COMMIT");
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
$t['list'][] = $this->prepareTaskRow($r);
$t['total'] = 1;
return $t;
}
private function editTask(int $id)
{
$db = DBConnection::instance();
$title = trim($this->req->jsonBody['title'] ?? '');
$note = str_replace("\r\n", "\n", $this->req->jsonBody['note'] ?? '');
$prio = (int)($this->req->jsonBody['prio'] ?? 0);
if ($prio < -1) $prio = -1;
elseif ($prio > 2) $prio = 2;
$duedate = $this->parseDuedate(trim( $this->req->jsonBody['duedate'] ?? '' ));
$t = array();
$t['total'] = 0;
if ($title == '') {
return $t;
}
$listId = (int) $db->sq("SELECT list_id FROM {$db->prefix}todolist WHERE id=$id");
$tags = trim( $this->req->jsonBody['tags'] ?? '' );
$db->ex("BEGIN");
$db->ex("DELETE FROM {$db->prefix}tag2task WHERE task_id=$id");
$aTags = $this->prepareTags($tags);
if($aTags) {
$tags = implode(',', $aTags['tags']);
$tags_ids = implode(',',$aTags['ids']);
$this->addTaskTags($id, $aTags['ids'], $listId);
}
$db->dq("UPDATE {$db->prefix}todolist SET title=?,note=?,prio=?,tags=?,tags_ids=?,duedate=?,d_edited=? WHERE id=$id",
array($title, $note, $prio, $tags, $tags_ids, $duedate, time()) );
$db->ex("COMMIT");
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
if ($r) {
$t['list'][] = $this->prepareTaskRow($r);
$t['total'] = 1;
}
return $t;
}
private function moveTask(int $id)
{
$db = DBConnection::instance();
$fromId = (int)($this->req->jsonBody['from'] ?? 0);
$toId = (int)($this->req->jsonBody['to'] ?? 0);
$result = $this->doMoveTask($id, $toId);
$t = array('total' => $result ? 1 : 0);
if ($fromId == -1 && $result && $r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id")) {
$t['list'][] = $this->prepareTaskRow($r);
}
return $t;
}
private function doMoveTask(int $id, int $listId)
{
$db = DBConnection::instance();
// Check task exists and not in target list
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=?", array($id));
if (!$r || $listId == $r['list_id']) return false;
// Check target list exists
if (!$db->sq("SELECT COUNT(*) FROM {$db->prefix}lists WHERE id=?", $listId))
return false;
$ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}todolist WHERE list_id=? AND compl=?", array($listId, $r['compl']?1:0));
$db->ex("BEGIN");
$db->ex("UPDATE {$db->prefix}tag2task SET list_id=? WHERE task_id=?", array($listId, $id));
$db->dq("UPDATE {$db->prefix}todolist SET list_id=?, ow=?, d_edited=? WHERE id=?", array($listId, $ow, time(), $id));
$db->ex("COMMIT");
return true;
}
private function completeTask(int $id)
{
$db = DBConnection::instance();
$compl = (int)($this->req->jsonBody['compl'] ?? 0);
$listId = (int)$db->sq("SELECT list_id FROM {$db->prefix}todolist WHERE id=$id");
if ($compl) $ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}todolist WHERE list_id=$listId AND compl=1");
else $ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}todolist WHERE list_id=$listId AND compl=0");
$dateCompleted = $compl ? time() : 0;
$db->dq("UPDATE {$db->prefix}todolist SET compl=$compl,ow=$ow,d_completed=?,d_edited=? WHERE id=$id",
array($dateCompleted, time()) );
$t = array();
$t['total'] = 1;
$r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id");
$t['list'][] = $this->prepareTaskRow($r);
return $t;
}
private function editNote(int $id)
{
$db = DBConnection::instance();
$note = $this->req->jsonBody['note'] ?? '';
$note = str_replace("\r\n", "\n", $note);
$db->dq("UPDATE {$db->prefix}todolist SET note=?,d_edited=? WHERE id=$id", array($note, time()) );
$t = array();
$t['total'] = 1;
$t['list'][] = array('id'=>$id, 'note'=> noteMarkup($note), 'noteText'=>(string)$note);
return $t;
}
private function priorityTask(int $id)
{
$db = DBConnection::instance();
$prio = (int)($this->req->jsonBody['prio'] ?? 0);
if ($prio < -1) $prio = -1;
elseif ($prio > 2) $prio = 2;
$db->ex("UPDATE {$db->prefix}todolist SET prio=$prio,d_edited=? WHERE id=$id", array(time()) );
$t = array();
$t['total'] = 1;
$t['list'][] = array('id'=>$id, 'prio'=>$prio);
return $t;
}
private function changeTaskOrder()
{
$db = DBConnection::instance();
$order = $this->req->jsonBody['order'] ?? null;
$t = array();
$t['total'] = 0;
if (is_array($order))
{
$ad = array();
foreach ($order as $obj) {
$id = $obj['id'] ?? 0;
$diff = $obj['diff'] ?? 0;
$ad[(int)$diff][] = (int)$id;
}
$db->ex("BEGIN");
foreach ($ad as $diff=>$ids) {
if ($diff >=0) $set = "ow=ow+".$diff;
else $set = "ow=ow-".abs($diff);
$db->dq("UPDATE {$db->prefix}todolist SET $set,d_edited=? WHERE id IN (".implode(',',$ids).")", array(time()) );
}
$db->ex("COMMIT");
$t['total'] = 1;
}
return $t;
}
private function getUserListsSimple()
{
$db = DBConnection::instance();
$a = array();
$q = $db->dq("SELECT id,name FROM {$db->prefix}lists ORDER BY id ASC");
while($r = $q->fetchRow()) {
$a[$r[0]] = $r[1];
}
return $a;
}
private function prepareTaskRow(array $r)
{
$lang = Lang::instance();
$dueA = $this->prepareDuedate($r['duedate']);
$formatCreatedInline = $formatCompletedInline = Config::get('dateformatshort');
if (date('Y') != date('Y', (int)$r['d_created']))
$formatCreatedInline = Config::get('dateformat2');
if ($r['d_completed'] && date('Y') != date('Y', (int)$r['d_completed']))
$formatCompletedInline = Config::get('dateformat2');
$dCreated = timestampToDatetime($r['d_created']);
$dCompleted = $r['d_completed'] ? timestampToDatetime($r['d_completed']) : '';
return array(
'id' => $r['id'],
'title' => titleMarkup( $r['title'] ),
'titleText' => (string)$r['title'],
'listId' => $r['list_id'],
'date' => htmlarray($dCreated),
'dateInt' => (int)$r['d_created'],
'dateInline' => htmlarray(formatTime($formatCreatedInline, $r['d_created'])),
'dateInlineTitle' => htmlarray(sprintf($lang->get('taskdate_inline_created'), $dCreated)),
'dateEditedInt' => (int)$r['d_edited'],
'dateCompleted' => htmlarray($dCompleted),
'dateCompletedInline' => $r['d_completed'] ? htmlarray(formatTime($formatCompletedInline, $r['d_completed'])) : '',
'dateCompletedInlineTitle' => htmlarray(sprintf($lang->get('taskdate_inline_completed'), $dCompleted)),
'compl' => (int)$r['compl'],
'prio' => $r['prio'],
'note' => noteMarkup($r['note']),
'noteText' => (string)$r['note'],
'ow' => (int)$r['ow'],
'tags' => htmlarray($r['tags']),
'tags_ids' => htmlarray($r['tags_ids']),
'duedate' => $dueA['formatted'],
'dueClass' => $dueA['class'],
'dueStr' => htmlarray($r['compl'] && $dueA['timestamp'] ? formatTime($formatCompletedInline, $dueA['timestamp']) : $dueA['str']),
'dueInt' => $this->date2int($r['duedate']),
'dueTitle' => htmlarray(sprintf($lang->get('taskdate_inline_duedate'), $dueA['formattedlong'])),
);
}
private function parseDuedate($s)
{
$df2 = Config::get('dateformat2');
if (max((int)strpos($df2,'n'), (int)strpos($df2,'m')) > max((int)strpos($df2,'d'), (int)strpos($df2,'j'))) $formatDayFirst = true;
else $formatDayFirst = false;
$y = $m = $d = 0;
if (preg_match("|^(\d+)-(\d+)-(\d+)\b|", $s, $ma)) {
$y = (int)$ma[1]; $m = (int)$ma[2]; $d = (int)$ma[3];
}
elseif (preg_match("|^(\d+)\/(\d+)\/(\d+)\b|", $s, $ma))
{
if($formatDayFirst) {
$d = (int)$ma[1]; $m = (int)$ma[2]; $y = (int)$ma[3];
} else {
$m = (int)$ma[1]; $d = (int)$ma[2]; $y = (int)$ma[3];
}
}
elseif (preg_match("|^(\d+)\.(\d+)\.(\d+)\b|", $s, $ma)) {
$d = (int)$ma[1]; $m = (int)$ma[2]; $y = (int)$ma[3];
}
elseif (preg_match("|^(\d+)\.(\d+)\b|", $s, $ma)) {
$d = (int)$ma[1]; $m = (int)$ma[2];
$a = explode(',', date('Y,m,d'));
if( $m<(int)$a[1] || ($m==(int)$a[1] && $d<(int)$a[2]) ) $y = (int)$a[0]+1;
else $y = (int)$a[0];
}
elseif (preg_match("|^(\d+)\/(\d+)\b|", $s, $ma))
{
if($formatDayFirst) {
$d = (int)$ma[1]; $m = (int)$ma[2];
} else {
$m = (int)$ma[1]; $d = (int)$ma[2];
}
$a = explode(',', date('Y,m,d'));
if( $m<(int)$a[1] || ($m==(int)$a[1] && $d<(int)$a[2]) ) $y = (int)$a[0]+1;
else $y = (int)$a[0];
}
else return null;
if ($y < 100) $y = 2000 + $y;
elseif ($y < 1000 || $y > 2099) $y = 2000 + (int)substr((string)$y, -2);
if ($m > 12) $m = 12;
$maxdays = $this->daysInMonth($m,$y);
if ($m < 10) $m = '0'.$m;
if ($d > $maxdays) $d = $maxdays;
elseif ($d < 10) $d = '0'.$d;
return "$y-$m-$d";
}
private function prepareDuedate($duedate)
{
$lang = Lang::instance();
$a = array( 'class'=>'', 'str'=>'', 'formatted'=>'', 'formattedlong'=>'', 'timestamp'=>0 );
if ($duedate == '') {
return $a;
}
$ad = explode('-', $duedate);
$at = explode('-', date('Y-m-d'));
$a['timestamp'] = mktime(0,0,0, (int)$ad[1], (int)$ad[2], (int)$ad[0]);
$diff = mktime(0,0,0, (int)$ad[1], (int)$ad[2], (int)$ad[0]) - mktime(0,0,0, (int)$at[1], (int)$at[2], (int)$at[0]);
if ($diff < -604800 && $ad[0] == $at[0]) { $a['class'] = 'past'; $a['str'] = formatDate3(Config::get('dateformatshort'), (int)$ad[0], (int)$ad[1], (int)$ad[2], $lang); }
elseif ($diff < -604800) { $a['class'] = 'past'; $a['str'] = formatDate3(Config::get('dateformat2'), (int)$ad[0], (int)$ad[1], (int)$ad[2], $lang); }
elseif ($diff < -86400) { $a['class'] = 'past'; $a['str'] = sprintf($lang->get('daysago'),ceil(abs($diff)/86400)); }
elseif ($diff < 0) { $a['class'] = 'past'; $a['str'] = $lang->get('yesterday'); }
elseif ($diff < 86400) { $a['class'] = 'today'; $a['str'] = $lang->get('today'); }
elseif ($diff < 172800) { $a['class'] = 'today'; $a['str'] = $lang->get('tomorrow'); }
elseif ($diff < 691200) { $a['class'] = 'soon'; $a['str'] = sprintf($lang->get('indays'),ceil($diff/86400)); }
elseif ($ad[0] == $at[0]) { $a['class'] = 'future'; $a['str'] = formatDate3(Config::get('dateformatshort'), (int)$ad[0], (int)$ad[1], (int)$ad[2], $lang); }
else { $a['class'] = 'future'; $a['str'] = formatDate3(Config::get('dateformat2'), (int)$ad[0], (int)$ad[1], (int)$ad[2], $lang); }
#avoid short year
$fmt = str_replace('y', 'Y', Config::get('dateformat2'));
$a['formatted'] = formatTime($fmt, $a['timestamp']);
$a['formattedlong'] = formatTime(Config::get('dateformat'), $a['timestamp']);
return $a;
}
private function date2int($d)
{
if (!$d) return 33330000;
$ad = explode('-', $d);
$s = $ad[0];
if (strlen($ad[1]) < 2) $s .= "0$ad[1]"; else $s .= $ad[1];
if (strlen($ad[2]) < 2) $s .= "0$ad[2]"; else $s .= $ad[2];
return (int)$s;
}
private function daysInMonth(int $m, int $y = 0)
{
if ($y == 0) $y = (int)date('Y');
$a = array(1=>31,(($y-2000)%4?28:29),31,30,31,30,31,31,30,31,30,31);
if (isset($a[$m])) return $a[$m];
else return 0;
}
private function getTagId($tag)
{
$db = DBConnection::instance();
$id = $db->sq("SELECT id FROM {$db->prefix}tags WHERE name=?", array($tag));
return $id ? $id : 0;
}
private function getOrCreateTag($name)
{
$db = DBConnection::instance();
$tagId = $db->sq("SELECT id FROM {$db->prefix}tags WHERE name=?", array($name));
if ($tagId)
return array('id'=>$tagId, 'name'=>$name);
$db->ex("INSERT INTO {$db->prefix}tags (name) VALUES (?)", array($name));
return array(
'id' => $db->lastInsertId(),
'name' => $name
);
}
private function prepareTags(string $tagsStr)
{
$tags = explode(',', $tagsStr);
if (!$tags) return 0;
$aTags = array('tags'=>array(), 'ids'=>array());
foreach ($tags as $tag)
{
$tag = str_replace(array('^','#'),'',trim($tag));
if ($tag == '') continue;
$aTag = $this->getOrCreateTag($tag);
if ($aTag && !in_array($aTag['id'], $aTags['ids'])) {
$aTags['tags'][] = $aTag['name'];
$aTags['ids'][] = $aTag['id'];
}
}
return $aTags;
}
private function addTaskTags(int $taskId, array $tagIds, int $listId)
{
$db = DBConnection::instance();
if (!$tagIds) return;
foreach ($tagIds as $tagId) {
$db->ex(
"INSERT INTO {$db->prefix}tag2task (task_id,tag_id,list_id) VALUES (?,?,?)",
array($taskId, $tagId, $listId)
);
}
}
private function parseSmartSyntax($title)
{
$a = [
'prio' => 0,
'title' => $title,
'tags' => ''
];
if ( preg_match("|^([-+]{1}\d+)(.+)|", $a['title'], $m) ) {
$a['prio'] = (int) $m[1];
if ( $a['prio'] < -1 ) $a['prio'] = -1;
elseif ( $a['prio'] > 2 ) $a['prio'] = 2;
$a['title'] = trim($m[2]);
}
$tags = [];
$a['title'] = trim( preg_replace_callback(
"/(?:^|\s+)#([^#\s]+)/",
function ($matches) use (&$tags) {
$tags[] = $matches[1];
return '';
},
$a['title']
) );
if (count($tags) > 0) {
$a['tags'] = implode( ',' , $tags );
}
return $a;
}
}

View file

@ -84,7 +84,10 @@ function getRequestUri()
{
// Do not use HTTP_X_REWRITE_URL due to CVE-2018-14773
// SCRIPT_NAME or PATH_INFO ?
if (isset($_SERVER['REQUEST_URI'])) {
if (isset($_SERVER['SCRIPT_NAME'])) {
return $_SERVER['SCRIPT_NAME'];
}
elseif (isset($_SERVER['REQUEST_URI'])) {
return $_SERVER['REQUEST_URI'];
}
else if (isset($_SERVER['ORIG_PATH_INFO'])) // IIS 5.0 CGI

View file

@ -50,6 +50,7 @@ var mytinytodo = window.mytinytodo = _mtt = {
menus: {},
mttUrl: '',
homeUrl: '',
apiUrl: '',
options: {
token: '',
title: '',
@ -119,9 +120,9 @@ var mytinytodo = window.mytinytodo = _mtt = {
lastHistoryState: null,
// procs
setApi: function(storage)
setApiDriver: function(driver)
{
this.db = new storage(this);
this.db = new driver(_mtt);
return this;
},
@ -136,6 +137,13 @@ var mytinytodo = window.mytinytodo = _mtt = {
this.mttUrl = options.mttUrl;
delete options.mttUrl;
}
if (options.hasOwnProperty('apiUrl')) {
this.apiUrl = options.apiUrl;
delete options.apiUrl;
}
else {
this.apiUrl = this.mttUrl + 'api.php/';
}
if (options.hasOwnProperty('db')) {
delete options.db;
}
@ -546,9 +554,9 @@ var mytinytodo = window.mytinytodo = _mtt = {
$(document).ajaxError(function(event, request, settings){
var errtxt;
if(request.status == 0) errtxt = 'Bad connection';
if (request.status == 0) errtxt = 'Bad connection';
else if(request.status == 403) errtxt = request.responseText;
else if(request.status != 200) errtxt = 'HTTP: '+request.status+'/'+request.statusText;
else if (request.status != 200) errtxt = 'HTTP: '+request.status+'/'+request.statusText + "\n" + request.responseText;
else errtxt = request.responseText;
flashError(_mtt.lang.get('error'), errtxt);
});
@ -1729,6 +1737,7 @@ function loadTags(listId, callback)
else tagsList = json.cloud;
var cloud = '';
$.each(tagsList, function(i,item){
// item.tag is escaped with htmlspecialchars()
cloud += ' <a href="#" tag="'+item.tag+'" tagid="'+item.id+'" class="tag w'+item.w+'" >'+item.tag+'</a>';
});
$('#tagcloudcontent').html(cloud)
@ -2487,7 +2496,7 @@ function showLogin()
function doAuth(form)
{
$.post(mytinytodo.mttUrl+'ajax.php?login', { login:1, password: form.password.value }, function(json){
_mtt.db.request( 'login', { password: form.password.value }, function(json) {
form.password.value = '';
if(json.logged)
{
@ -2498,16 +2507,16 @@ function doAuth(form)
flashError(_mtt.lang.get('invalidpass'));
$('#password').focus();
}
}, 'json');
});
}
function logout()
{
$.post(mytinytodo.mttUrl+'ajax.php?logout', { logout:1 }, function(json){
_mtt.db.request( 'logout', {}, function(json) {
flag.isLogged = false;
window.location.hash = '';
window.location.reload();
}, 'json');
});
return false;
}

View file

@ -1,179 +0,0 @@
/*
This file is a part of myTinyTodo.
(C) Copyright 2010,2020 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
// AJAX myTinyTodo Storage
(function(){
"use strict";
var mtt;
function mytinytodoStorageAjax(amtt)
{
this.mtt = mtt = amtt;
}
window.mytinytodoStorageAjax = mytinytodoStorageAjax;
mytinytodoStorageAjax.prototype =
{
/* required method */
request:function(action, params, callback)
{
if(!this[action]) throw "Unknown storage action: "+action;
this[action](params, function(json){
if(json.denied) mtt.errorDenied();
if(callback) callback.call(mtt, json)
});
},
loadLists: function(params, callback)
{
$.getJSON(this.mtt.mttUrl+'ajax.php?loadLists', callback);
},
loadTasks: function(params, callback)
{
var q = '';
if(params.search && params.search != '') q += '&s='+encodeURIComponent(params.search);
if(params.tag && params.tag != '') q += '&t='+encodeURIComponent(params.tag);
if(params.setCompl && params.setCompl != 0) q += '&setCompl=1';
$.getJSON(this.mtt.mttUrl+'ajax.php?loadTasks&list='+params.list+'&compl='+params.compl+'&sort='+params.sort+q, callback);
},
newTask: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?newTask',
{ list:params.list, title: params.title, tag:params.tag }, callback, 'json');
},
fullNewTask: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?fullNewTask',
{ list:params.list, title:params.title, note:params.note, prio:params.prio, tags:params.tags, duedate:params.duedate },
callback, 'json');
},
editTask: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?editTask='+params.id,
{ id:params.id, title:params.title, note:params.note, prio:params.prio, tags:params.tags, duedate:params.duedate },
callback, 'json');
},
editNote: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?editNote='+params.id, {id:params.id, note: params.note}, callback, 'json');
},
completeTask: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?completeTask='+params.id, { id:params.id, compl:params.compl }, callback, 'json');
},
deleteTask: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?deleteTask='+params.id, { id:params.id }, callback, 'json');
},
setTaskPriority: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?setTaskPriority='+params.id, { id:params.id, priority:params.priority }, callback, 'json');
},
setSort: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?setSort', { list:params.list, sort:params.sort }, callback, 'json');
},
changeOrder: function(params, callback)
{
var order = '';
for(var i in params.order) {
order += params.order[i].id +'='+ params.order[i].diff + '&';
}
$.post(this.mtt.mttUrl+'ajax.php?changeOrder', { order:order }, callback, 'json');
},
suggestTags: function(params, callback)
{
$.getJSON(this.mtt.mttUrl+'ajax.php?suggestTags', {list:params.list, q:params.q}, callback);
},
tagCloud: function(params, callback)
{
$.getJSON(this.mtt.mttUrl+'ajax.php?tagCloud&list='+params.list, callback);
},
moveTask: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?moveTask', { id:params.id, from:params.from, to:params.to }, callback, 'json');
},
parseTaskStr: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?parseTaskStr', { list:params.list, title:params.title, tag:params.tag }, callback, 'json');
},
// Lists
addList: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?addList', { name:params.name }, callback, 'json');
},
renameList: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?renameList', { list:params.list, name:params.name }, callback, 'json');
},
deleteList: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?deleteList', { list:params.list }, callback, 'json');
},
publishList: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?publishList', { list:params.list, publish:params.publish }, callback, 'json');
},
setShowNotesInList: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?setShowNotesInList', { list:params.list, shownotes:params.shownotes }, callback, 'json');
},
setHideList: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?setHideList', { list:params.list, hide:params.hide }, callback, 'json');
},
changeListOrder: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?changeListOrder', { order:params.order }, callback, 'json');
},
clearCompletedInList: function(params, callback)
{
$.post(this.mtt.mttUrl+'ajax.php?clearCompletedInList', { list:params.list }, callback, 'json');
}
};
})();

View file

@ -0,0 +1,385 @@
/*
This file is a part of myTinyTodo.
(C) Copyright 2010,2020,2022 Max Pozdeev <maxpozdeev@gmail.com>
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
(function(){
"use strict";
var mtt;
function MytinytodoAjaxApi(amtt)
{
mtt = amtt;
}
window.MytinytodoAjaxApi = MytinytodoAjaxApi;
MytinytodoAjaxApi.prototype =
{
/* required method */
request: function(action, params, callback)
{
if (!this[action]) throw "Unknown ApiDriver action: " + action;
this[action] (params, function(json){
if (json.denied) mtt.errorDenied();
if (callback) callback.call(mtt, json)
});
},
loadTasks: function(params, callback)
{
var q = '';
if (params.search && params.search != '') q += '&s=' + encodeURIComponent(params.search);
if (params.tag && params.tag != '') q += '&t=' + encodeURIComponent(params.tag);
if (params.setCompl && params.setCompl != 0) q += '&setCompl=1';
$.getJSON(mtt.apiUrl + 'tasks?list='+params.list+'&compl='+params.compl+'&sort='+params.sort+q, callback);
},
newTask: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'tasks',
method: 'POST',
contentType : 'application/json',
data: JSON.stringify({
action: 'simple',
list: params.list,
title: params.title,
tag: params.tag,
}),
success: callback,
dataType: 'json'
});
},
fullNewTask: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'tasks',
method: 'POST',
contentType : 'application/json',
data: JSON.stringify({
action: 'full',
list: params.list,
title: params.title,
note: params.note,
prio: params.prio,
tags: params.tags,
duedate: params.duedate,
/* tag: params.tag, // We do not send current tag filter, autotag should set it in the form and include in tags */
}),
success: callback,
dataType: 'json'
});
},
editTask: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'edit',
title: params.title,
note: params.note,
prio: params.prio,
tags: params.tags,
duedate: params.duedate,
}),
success: callback,
dataType: 'json'
});
},
editNote: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'note',
note: params.note
}),
success: callback,
dataType: 'json'
});
},
completeTask: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'complete',
compl: params.compl
}),
success: callback,
dataType: 'json'
});
},
deleteTask: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: 'DELETE',
success: callback,
dataType: 'json'
});
},
setTaskPriority: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'priority',
prio: params.priority,
}),
success: callback,
dataType: 'json'
});
},
changeOrder: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'tasks',
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'order',
order: params.order,
}),
success: callback,
dataType: 'json'
});
},
suggestTags: function(params, callback)
{
$.getJSON(mtt.apiUrl + 'suggestTags', {list:params.list, q:params.q}, callback);
},
tagCloud: function(params, callback)
{
$.getJSON(mtt.apiUrl + 'tagCloud/' + encodeURIComponent(params.list), callback);
},
moveTask: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'tasks/' + encodeURIComponent(params.id),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'move',
from: params.from,
to: params.to
}),
success: callback,
dataType: 'json'
});
},
parseTaskStr: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'tasks/parseTitle',
method: 'POST',
contentType : 'application/json',
data: JSON.stringify({
list: params.list,
title: params.title,
tag: params.tag ,
}),
success: callback,
dataType: 'json'
});
},
// Lists
loadLists: function(params, callback)
{
$.getJSON(mtt.apiUrl + 'lists', callback);
},
addList: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'lists',
method: 'POST',
contentType : 'application/json',
data: JSON.stringify({
name: params.name,
}),
success: callback,
dataType: 'json'
});
},
deleteList: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'DELETE',
success: callback,
dataType: 'json'
});
},
renameList: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'rename',
name: params.name,
}),
success: callback,
dataType: 'json'
});
},
setSort: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'sort',
name: params.name,
sort: params.sort
}),
success: callback,
dataType: 'json'
});
},
publishList: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'publish',
publish: params.publish,
}),
success: callback,
dataType: 'json'
});
},
setShowNotesInList: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'showNotes',
shownotes: params.shownotes,
}),
success: callback,
dataType: 'json'
});
},
setHideList: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'hide',
hide: params.hide,
}),
success: callback,
dataType: 'json'
});
},
changeListOrder: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'lists',
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'order',
order: params.order
}),
success: callback,
dataType: 'json'
});
},
clearCompletedInList: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'lists/' + encodeURIComponent(params.list),
method: 'PUT',
contentType : 'application/json',
data: JSON.stringify({
action: 'clearCompleted',
}),
success: callback,
dataType: 'json'
});
},
/* Auth */
login: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'login',
method: 'POST',
contentType : 'application/json',
data: JSON.stringify({
password: params.password,
}),
success: callback,
dataType: 'json'
});
},
logout: function(params, callback)
{
$.ajax({
url: mtt.apiUrl + 'logout',
method: 'POST',
success: callback,
dataType: 'json'
});
}
};
})();

View file

@ -73,6 +73,7 @@ function js_options()
"lang" => Lang::instance()->jsStrings(),
"mttUrl" => get_mttinfo('mtt_url'),
"homeUrl" => get_mttinfo('url'),
// "apiUrl" => get_mttinfo('api_url'),
"needAuth" => need_auth() ? true : false,
"isLogged" => is_logged() ? true : false,
"showdate" => Config::get('showdate') ? true : false,

View file

@ -157,7 +157,7 @@ function check_token()
$token = access_token();
if ($token == '' || !isset($_SERVER['HTTP_MTT_TOKEN']) || $_SERVER['HTTP_MTT_TOKEN'] != $token) {
http_response_code(403);
die("Access denied! Try to reload the page.");
die("Access denied! You must authenticate first.");
}
}
@ -268,12 +268,19 @@ function get_unsafe_mttinfo($v)
}
return $_mttinfo['url'];
case 'mtt_url':
/* Directory with ajax.php. No need to set if you use default directory structure. */
/* Directory with settings.php. No need to set if you use default directory structure. */
$_mttinfo['mtt_url'] = Config::getUrl('mtt_url'); // need to have a trailing slash
if ($_mttinfo['mtt_url'] == '') {
$_mttinfo['mtt_url'] = url_dir( get_unsafe_mttinfo('url'), 0 );
}
return $_mttinfo['mtt_url'];
case 'api_url':
/* URL for API, like http://localhost/mytinytodo/api/. No need to set by default. */
$_mttinfo['api_url'] = Config::getUrl('api_url'); // need to have a trailing slash
if ($_mttinfo['api_url'] == '') {
$_mttinfo['api_url'] = get_unsafe_mttinfo('mtt_url'). 'api/';
}
return $_mttinfo['api_url'];
case 'title':
$_mttinfo['title'] = (Config::get('title') != '') ? Config::get('title') : __('My Tiny Todolist');
return $_mttinfo['title'];