mirror of
https://github.com/maxpozdeev/mytinytodo.git
synced 2026-03-11 08:55:27 +00:00
* change HTTP API
This commit is contained in:
parent
90278d0f63
commit
490b64c2b3
14 changed files with 1758 additions and 1162 deletions
12
src/.htaccess
Normal file
12
src/.htaccess
Normal 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;
|
||||
# }
|
||||
970
src/ajax.php
970
src/ajax.php
|
|
@ -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
227
src/api.php
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
63
src/includes/api/AuthController.php
Normal file
63
src/includes/api/AuthController.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
293
src/includes/api/ListsController.php
Normal file
293
src/includes/api/ListsController.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
83
src/includes/api/TagsController.php
Normal file
83
src/includes/api/TagsController.php
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
662
src/includes/api/TasksController.php
Normal file
662
src/includes/api/TasksController.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
})();
|
||||
385
src/includes/mytinytodo_api.js
Normal file
385
src/includes/mytinytodo_api.js
Normal 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'
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
11
src/init.php
11
src/init.php
|
|
@ -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'];
|
||||
|
|
|
|||
Loading…
Reference in a new issue