diff --git a/src/export.php b/src/export.php index 4ff7d04..091439f 100644 --- a/src/export.php +++ b/src/export.php @@ -24,22 +24,14 @@ if (!$listData) { die("No list found."); } -$sqlSort = "ORDER BY compl ASC, "; -if($listData['sorting'] == 1) $sqlSort .= "prio DESC, ddn ASC, duedate ASC, ow ASC"; -elseif($listData['sorting'] == 2) $sqlSort .= "ddn ASC, duedate ASC, prio DESC, ow ASC"; -else $sqlSort .= "ow ASC"; +$data = DBCore::defaultInstance()->getTasksByListId($listId, '', (int)$listData['sorting']); -$data = array(); -$q = $db->dq("SELECT *, duedate IS NULL AS ddn FROM {$db->prefix}todolist WHERE list_id=$listId $sqlSort"); -while($r = $q->fetchAssoc()) -{ - $data[] = $r; +if (_get('format') == 'ical') { + printICal($listData, $data); +} +else { + printCSV($listData, $data); } - -$format = _get('format'); - -if($format == 'ical') printICal($listData, $data); -else printCSV($listData, $data); function printCSV(array $listData, array $data) diff --git a/src/feed.php b/src/feed.php index 0475fb1..ebe2d61 100644 --- a/src/feed.php +++ b/src/feed.php @@ -32,7 +32,7 @@ $feedType = _get('feed'); if($feedType == 'completed') { $listData['_feed_descr'] = $lang->get('feed_completed_tasks'); - fillData( $data, $listId, 'd_completed', 'AND compl=1' ); + fillData( $data, $listId, 'd_completed', 'compl=1' ); } elseif($feedType == 'modified') { $listData['_feed_descr'] = $lang->get('feed_modified_tasks'); @@ -40,13 +40,13 @@ elseif($feedType == 'modified') { } elseif($feedType == 'current') { $listData['_feed_descr'] = $lang->get('feed_new_tasks'); - fillData( $data, $listId, 'd_created', 'AND compl=0' ); + fillData( $data, $listId, 'd_created', 'compl=0' ); } elseif($feedType == 'status') { $listData['_feed_descr'] = $lang->get('feed_tasks'); fillData( $data, $listId, 'd_created', '' ); - fillData( $data, $listId, 'd_edited', 'AND compl=0 AND d_edited > d_created' ); - fillData( $data, $listId, 'd_completed', 'AND compl=1' ); + fillData( $data, $listId, 'd_edited', 'compl=0 AND d_edited > d_created' ); + fillData( $data, $listId, 'd_completed', 'compl=1' ); } else { $listData['_feed_descr'] = $lang->get('feed_new_tasks'); @@ -64,15 +64,14 @@ printRss($data, $listData); function fillData(array &$data, int $listId, string $field, string $sqlWhere ) { + $tasks = DBCore::defaultInstance()->getTasksByListId($listId, $sqlWhere, "$field DESC", 100); $lang = Lang::instance(); - $db = DBConnection::instance(); - $q = $db->dq("SELECT * FROM {$db->prefix}todolist WHERE list_id=$listId $sqlWhere ORDER BY $field DESC LIMIT 100"); - while ($r = $q->fetchAssoc()) + foreach ($tasks as $r) { if ($r['prio'] > 0) { $r['prio'] = '+'.$r['prio']; } - $a = array(); + $a = array(); //for _descr $a[] = $lang->get('task'). ": ". $r['title']; if ($r['prio']) { $a[] = $lang->get('priority'). ": $r[prio]"; diff --git a/src/includes/api/TasksController.php b/src/includes/api/TasksController.php index 0e726b9..9440e42 100644 --- a/src/includes/api/TasksController.php +++ b/src/includes/api/TasksController.php @@ -98,10 +98,14 @@ class TasksController extends ApiController { $t['total'] = 0; $t['list'] = array(); - $q = $db->dq("SELECT todo.*, duedate IS NULL AS ddn + $q = $db->dq(" + SELECT todo.*, todo.duedate IS NULL AS ddn, GROUP_CONCAT(tags.id) AS tags_ids, GROUP_CONCAT(tags.name) AS tags FROM {$db->prefix}todolist AS todo + LEFT JOIN {$db->prefix}tag2task AS t2t ON todo.id = t2t.task_id + LEFT JOIN {$db->prefix}tags AS tags ON t2t.tag_id = tags.id WHERE $sqlWhereListId $sqlWhere - GROUP BY todo.id $sqlSort"); + GROUP BY todo.id $sqlSort + "); while ($r = $q->fetchAssoc()) { @@ -187,6 +191,11 @@ class TasksController extends ApiController { checkWriteAccess(); $id = (int)$id; + if (!DBCore::defaultInstance()->taskExists($id)) { + $this->response->data = ['total' => 0]; + return; + } + $action = $this->req->jsonBody['action'] ?? ''; switch ($action) { case 'edit': $this->response->data = $this->editTask($id); break; @@ -258,14 +267,12 @@ class TasksController extends ApiController { $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"); - $oo = $this->prepareTaskRow($r); - MTTNotificationCenter::postNotification(MTTNotification::didCreateTask, $oo); - $t['list'][] = $oo; + $task = $this->getTaskRowById($id); + MTTNotificationCenter::postNotification(MTTNotification::didCreateTask, $task); + $t['list'][] = $task; $t['total'] = 1; return $t; } @@ -298,14 +305,12 @@ class TasksController extends ApiController { $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"); - $oo = $this->prepareTaskRow($r); - MTTNotificationCenter::postNotification(MTTNotification::didCreateTask, $oo); - $t['list'][] = $oo; + $task = $this->getTaskRowById($id); + MTTNotificationCenter::postNotification(MTTNotification::didCreateTask, $task); + $t['list'][] = $task; $t['total'] = 1; return $t; } @@ -329,31 +334,26 @@ class TasksController extends ApiController { $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']); + if ($aTags) { $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->dq("UPDATE {$db->prefix}todolist SET title=?,note=?,prio=?,duedate=?,d_edited=? WHERE id=$id", + array($title, $note, $prio, $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; - } + $task = $this->getTaskRowById($id); + $t['list'][] = $task; + $t['total'] = 1; return $t; } private function moveTask(int $id): ?array { - $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); + if ($fromId == -1 && $result) { + $t['list'][] = $this->getTaskRowById($id); } return $t; } @@ -392,8 +392,7 @@ class TasksController extends ApiController { array($dateCompleted, $date) ); $t = array(); $t['total'] = 1; - $r = $db->sqa("SELECT * FROM {$db->prefix}todolist WHERE id=$id"); - $t['list'][] = $this->prepareTaskRow($r); + $t['list'][] = $this->getTaskRowById($id); return $t; } @@ -460,6 +459,15 @@ class TasksController extends ApiController { return $a; } + private function getTaskRowById(int $id): ?array + { + $r = DBCore::defaultInstance()->getTaskById($id); + if (!$r) { + throw new Exception("Failed to fetch task data"); + } + return $this->prepareTaskRow($r); + } + private function prepareTaskRow(array $r): array { $lang = Lang::instance(); @@ -487,7 +495,7 @@ class TasksController extends ApiController { 'date' => htmlarray($dCreated), 'dateInt' => (int)$r['d_created'], 'dateFull' => htmlarray($dCreatedFull), - 'dateInlineTitle' => htmlarray(sprintf($lang->get('taskdate_inline_created'), $dCreated)), + 'dateInlineTitle' => htmlarray(sprintf($lang->get('taskdate_inline_created'), $dCreated)), //TODO: move preparing of *inlineTitle to js 'dateEdited' => htmlarray($dEdited), 'dateEditedInt' => (int)$r['d_edited'], 'dateEditedFull' => htmlarray($dEditedFull), @@ -501,8 +509,8 @@ class TasksController extends ApiController { 'note' => noteMarkup($r['note']), 'noteText' => (string)$r['note'], 'ow' => (int)$r['ow'], - 'tags' => htmlarray($r['tags']), - 'tags_ids' => htmlarray($r['tags_ids']), + 'tags' => htmlarray($r['tags'] ?? ''), + 'tags_ids' => htmlarray($r['tags_ids'] ?? ''), 'duedate' => htmlarray($dueA['formatted']), 'dueClass' => $dueA['class'], 'dueStr' => htmlarray($dueA['str']), diff --git a/src/includes/class.dbcore.php b/src/includes/class.dbcore.php index 8aaa47f..ef7581e 100644 --- a/src/includes/class.dbcore.php +++ b/src/includes/class.dbcore.php @@ -74,5 +74,84 @@ class DBCore return $listId; } + + public function taskExists(int $id): bool + { + $db = $this->db; + $count = (int) $db->sq("SELECT COUNT(*) FROM {$db->prefix}todolist WHERE id = $id"); + return ($count > 0) ? true : false; + } + + + public function getTaskById(int $id): ?array + { + $db = $this->db; + $r = $db->sqa(" + SELECT todo.*, GROUP_CONCAT(tags.id) AS tags_ids, GROUP_CONCAT(tags.name) AS tags + FROM {$db->prefix}todolist AS todo + LEFT JOIN {$db->prefix}tag2task AS t2t ON todo.id = t2t.task_id + LEFT JOIN {$db->prefix}tags AS tags ON t2t.tag_id = tags.id + WHERE todo.id = $id + "); + return $r; + } + + /** + * + * @param int $listId + * @param string $sqlWhere + * @param int|string $sort + * @param null|int $limit + * @return array + */ + public function getTasksByListId(int $listId, string $sqlWhere, /* int|string */ $sort, ?int $limit = null): array + { + $db = $this->db; + + if ($sqlWhere != '') { + $sqlWhere = "AND $sqlWhere"; + } + + $sqlSort = ''; + if (is_int($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) + elseif ($sort == 5) $sqlSort .= "title ASC, prio DESC, ow ASC"; // byTitle + elseif ($sort == 105) $sqlSort .= "title DESC, prio ASC, ow DESC"; // byTitle (reverse) + else $sqlSort .= "ow ASC"; + } + else if ($sort != '') { + $sqlSort = "ORDER BY $sort"; + } + + $sqlLimit = ''; + if (!is_null($limit)) { + $sqlLimit = "LIMIT $limit"; + } + + $q = $db->dq(" + SELECT todo.*, todo.duedate IS NULL AS ddn, GROUP_CONCAT(tags.id) AS tags_ids, GROUP_CONCAT(tags.name) AS tags + FROM {$db->prefix}todolist AS todo + LEFT JOIN {$db->prefix}tag2task AS t2t ON todo.id = t2t.task_id + LEFT JOIN {$db->prefix}tags AS tags ON t2t.tag_id = tags.id + WHERE todo.list_id = $listId $sqlWhere + GROUP BY todo.id + $sqlSort + $sqlLimit + "); + + $data = array(); + while ($r = $q->fetchAssoc()) { + $data[] = $r; + } + return $data; + } } diff --git a/src/setup.php b/src/setup.php index b7c72c9..51a854b 100644 --- a/src/setup.php +++ b/src/setup.php @@ -278,8 +278,6 @@ function createMysqlTables($db) `note` TEXT, `prio` TINYINT NOT NULL default 0, /* priority -,0,+ */ `ow` INT NOT NULL default 0, /* order weight */ - `tags` VARCHAR(600) NOT NULL default '', /* for fast access to task tags */ - `tags_ids` VARCHAR(250) NOT NULL default '', /* no more than 22 tags (x11 chars) */ `duedate` DATE default NULL, PRIMARY KEY(`id`), KEY(`list_id`), @@ -290,7 +288,7 @@ function createMysqlTables($db) $db->ex( "CREATE TABLE {$db->prefix}tags ( `id` INT UNSIGNED NOT NULL auto_increment, - `name` VARCHAR(50) NOT NULL, + `name` VARCHAR(250) NOT NULL default '', PRIMARY KEY(`id`), UNIQUE KEY `name` (`name`) ) CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci "); @@ -333,7 +331,7 @@ function createSqliteTables($db) id INTEGER PRIMARY KEY, uuid CHAR(36) NOT NULL, ow INTEGER NOT NULL default 0, - name VARCHAR(50) NOT NULL, + name VARCHAR(250) NOT NULL, d_created INTEGER UNSIGNED NOT NULL default 0, d_edited INTEGER UNSIGNED NOT NULL default 0, sorting TINYINT UNSIGNED NOT NULL default 0, @@ -357,8 +355,6 @@ function createSqliteTables($db) note TEXT COLLATE UTF8CI default NULL, prio TINYINT NOT NULL default 0, ow INTEGER NOT NULL default 0, - tags VARCHAR(600) NOT NULL default '', - tags_ids VARCHAR(250) NOT NULL default '', duedate DATE default NULL ) "); $db->ex("CREATE INDEX todo_list_id ON {$db->prefix}todolist (list_id)"); @@ -422,6 +418,8 @@ function databaseVersion(Database_Abstract $db): string $v = '1.4'; if ( !$db->tableExists($db->prefix.'settings') ) return $v; $v = '1.7'; + if ( $db->tableFieldExists($db->prefix.'todolist', 'tags') ) return $v; + $v = '1.8'; return $v; } @@ -579,7 +577,7 @@ function update_14_17(Database_Abstract $db, $dbtype) $db->ex("ALTER TABLE {$db->prefix}lists ADD `extra` TEXT"); # increase the length of list and tag name - # (not applicable to sqlite because it uses VARCHAR fields of eny length as TEXT) + # (not applicable to sqlite because it uses VARCHAR fields of any length as TEXT) $db->ex("ALTER TABLE {$db->prefix}todolist CHANGE `tags` `tags` VARCHAR(2000) NOT NULL default '' "); $db->ex("ALTER TABLE {$db->prefix}tags CHANGE `name` `name` VARCHAR(250) NOT NULL default '' "); $db->ex("ALTER TABLE {$db->prefix}lists CHANGE `name` `name` VARCHAR(250) NOT NULL default '' "); @@ -653,7 +651,7 @@ function update_17_18(Database_Abstract $db, $dbtype) if ($dbtype == 'sqlite') { - // Use UTF8CI collate. Old sqlite does not support DROP COLUMN + // Use UTF8CI collate. Old sqlite does not support DROP COLUMN (before v3.35.0 2021-03-12) $db->ex("DROP INDEX todo_list_id"); $db->ex("DROP INDEX todo_uuid"); $db->ex("ALTER TABLE {$db->prefix}todolist RENAME TO {$db->prefix}todolist_old"); @@ -670,11 +668,9 @@ function update_17_18(Database_Abstract $db, $dbtype) note TEXT COLLATE UTF8CI default NULL, prio TINYINT NOT NULL default 0, ow INTEGER NOT NULL default 0, - tags VARCHAR(600) NOT NULL default '', - tags_ids VARCHAR(250) NOT NULL default '', duedate DATE default NULL )" ); - $db->ex("INSERT INTO {$db->prefix}todolist SELECT * FROM {$db->prefix}todolist_old"); + $db->ex("INSERT INTO {$db->prefix}todolist SELECT id,uuid,list_id,d_created,d_completed,d_edited,compl,title,note,prio,ow,duedate FROM {$db->prefix}todolist_old"); $db->ex("CREATE INDEX todo_list_id ON {$db->prefix}todolist (list_id)"); $db->ex("CREATE UNIQUE INDEX todo_uuid ON {$db->prefix}todolist (uuid)"); $db->ex("DROP TABLE {$db->prefix}todolist_old"); @@ -684,12 +680,20 @@ function update_17_18(Database_Abstract $db, $dbtype) $db->ex( "CREATE TABLE {$db->prefix}tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, - name VARCHAR(50) NOT NULL DEFAULT '' COLLATE UTF8CI )" + name VARCHAR(250) NOT NULL DEFAULT '' COLLATE UTF8CI )" ); $db->ex("INSERT INTO {$db->prefix}tags SELECT * FROM {$db->prefix}tags_old"); $db->ex("CREATE INDEX tags_name ON {$db->prefix}tags (name)"); $db->ex("DROP TABLE {$db->prefix}tags_old"); } + else // mysql + { + $db->ex("ALTER TABLE {$db->prefix}todolist DROP COLUMN tags"); + $db->ex("ALTER TABLE {$db->prefix}todolist DROP COLUMN tags_ids"); + + // if mysql db was created in v1.7.x then tags.name field has length of 50 instead of 250 + $db->ex("ALTER TABLE {$db->prefix}tags CHANGE `name` `name` VARCHAR(250) NOT NULL default '' "); + } $db->ex("COMMIT");