mytinytodo/src/includes/api/TasksController.php

773 lines
28 KiB
PHP
Raw Normal View History

2022-07-10 12:23:53 +00:00
<?php declare(strict_types=1);
/*
This file is a part of myTinyTodo.
(C) Copyright 2022-2023 Max Pozdeev <maxpozdeev@gmail.com>
2022-07-10 12:23:53 +00:00
Licensed under the GNU GPL version 2 or any later. See file COPYRIGHT for details.
*/
require_once(MTTINC. 'markup.php');
require_once(MTTINC. 'smartsyntax.php');
2022-07-10 12:23:53 +00:00
class TasksController extends ApiController {
/**
* Get tasks.
* Filters are set with query parameters.
* @return void
2022-07-10 12:23:53 +00:00
* @throws Exception
*/
function get()
{
$listId = (int)_get('list');
checkReadAccess($listId);
$db = DBConnection::instance();
$dbcore = DBCore::default();
2022-07-10 12:23:53 +00:00
$sqlWhere = $sqlWhereListId = $sqlInnerWhereListId = '';
2022-07-10 12:23:53 +00:00
if ($listId == -1) {
$userLists = $this->getUserListsSimple();
$userListsIds = implode(',', array_keys($userLists));
$sqlWhereListId = "todo.list_id IN ($userListsIds) ";
$sqlInnerWhereListId = "list_id IN ($userListsIds) ";
2022-07-10 12:23:53 +00:00
}
else {
$sqlWhereListId = "todo.list_id=". $listId;
2022-07-10 12:23:53 +00:00
$sqlInnerWhereListId = "list_id=$listId ";
}
if (_get('compl') == 0) {
$sqlWhere .= ' AND compl=0';
}
$tag = trim(_get('t'));
if ($tag != '')
{
$at = explode(',', $tag);
$tagIds = array(); # [ [id1,id2], [id3]... ]
2022-07-10 12:23:53 +00:00
$tagExIds = array();
foreach ($at as $atv) {
2022-07-10 12:23:53 +00:00
$atv = trim($atv);
if ($atv == '' || $atv == '^') continue;
if (substr($atv,0,1) == '^') {
array_push($tagExIds, ...$dbcore->getTagIdsByName(substr($atv,1)));
2022-07-10 12:23:53 +00:00
} else {
$tagIds[] = $dbcore->getTagIdsByName($atv);
2022-07-10 12:23:53 +00:00
}
}
// Include tags
if (count($tagIds) > 0) {
$tagAnd = [];
foreach ($tagIds as $ids) {
$tagAnd[] = "task_id IN (SELECT task_id FROM {$db->prefix}tag2task WHERE tag_id IN (". implode(',', $ids). "))";
}
$sqlWhere .= "\n AND todo.id IN (".
"SELECT DISTINCT task_id FROM {$db->prefix}tag2task WHERE ". implode(' AND ', $tagAnd). ")";
2022-07-10 12:23:53 +00:00
}
// Exclude tags
if (count($tagExIds) > 0) {
$sqlWhere .= "\n AND todo.id NOT IN (SELECT DISTINCT task_id FROM {$db->prefix}tag2task ".
"WHERE tag_id IN (". implode(',', $tagExIds). "))";
2022-07-10 12:23:53 +00:00
}
}
$s = trim(_get('s'));
if ($s != '') {
if (preg_match("|^#(\d+)$|", $s, $m)) {
$sqlWhere .= " AND todo.id = ". (int)$m[1];
}
else {
$sqlWhere .= " AND (". $db->like("title", "%%%s%%", $s). " OR ". $db->like("note", "%%%s%%", $s). ")";
}
2022-07-10 12:23:53 +00:00
}
$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)
elseif ($sort == 5) $sqlSort .= "title ASC, prio DESC, ow ASC"; // byTitle
elseif ($sort == 105) $sqlSort .= "title DESC, prio ASC, ow DESC"; // byTitle (reverse)
2022-07-10 12:23:53 +00:00
else $sqlSort .= "ow ASC";
$t = array();
$t['total'] = 0;
$t['list'] = array();
$t['time'] = time();
2023-08-03 20:13:55 +00:00
$groupConcat = '';
if ($db::DBTYPE == DBConnection::DBTYPE_POSTGRES) {
$groupConcat = "array_to_string(array_agg(tags.id), ',') AS tags_ids, string_agg(tags.name, ',') AS tags";
}
else {
$groupConcat = "GROUP_CONCAT(tags.id) AS tags_ids, GROUP_CONCAT(tags.name) AS tags";
}
$q = $db->dq("
2023-08-03 20:13:55 +00:00
SELECT todo.*, todo.duedate IS NULL AS ddn, $groupConcat
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
2023-08-03 20:13:55 +00:00
GROUP BY todo.id
$sqlSort
");
2022-07-10 12:23:53 +00:00
while ($r = $q->fetchAssoc())
{
$t['total']++;
$t['list'][] = $this->prepareTaskRow($r);
}
if (_get('setCompl') && haveWriteAccess($listId)) {
ListsController::setListShowCompletedById($listId, !(_get('compl') == 0) );
2022-07-10 12:23:53 +00:00
}
if (_get('saveSort') == 1 && haveWriteAccess($listId)) {
ListsController::setListSortingById($listId, $sort);
}
$this->response->data = $t;
2022-07-10 12:23:53 +00:00
}
/**
* Create new task
* action: simple or full
* @return void
2022-07-10 12:23:53 +00:00
* @throws Exception
*/
function post()
{
$listId = (int)($this->req->jsonBody['list'] ?? 0);
checkWriteAccess($listId);
$action = $this->req->jsonBody['action'] ?? '';
if ($action == 'full') {
$this->response->data = $this->fullNewTaskInList($listId);
2022-07-10 12:23:53 +00:00
}
else {
$this->response->data = $this->newTaskInList($listId);
}
2022-07-10 12:23:53 +00:00
}
/**
* Actions with multiple tasks
* @return void
2022-07-10 12:23:53 +00:00
* @throws Exception
*/
function put()
{
checkWriteAccess();
$action = $this->req->jsonBody['action'] ?? '';
switch ($action) {
case 'order': $this->response->data = $this->changeTaskOrder(); break;
default: $this->response->data = ['total' => 0]; // error 400 ?
2022-07-10 12:23:53 +00:00
}
}
/**
* Delete task by Id
* @param mixed $id
* @return void
2022-07-10 12:23:53 +00:00
* @throws Exception
*/
function deleteId($id)
{
checkWriteAccess();
$id = (int)$id;
$task = null;
if (MTTNotificationCenter::hasObserversForNotification(MTTNotification::didDeleteTask)) {
$task = $this->getTaskRowById($id);
}
2022-07-10 12:23:53 +00:00
$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");
if ($deleted && MTTNotificationCenter::hasObserversForNotification(MTTNotification::didDeleteTask)) {
MTTNotificationCenter::postNotification(MTTNotification::didDeleteTask, $task);
}
2022-07-10 12:23:53 +00:00
$t = array();
$t['total'] = $deleted;
$t['list'][] = array('id' => $id);
$this->response->data = $t;
2022-07-10 12:23:53 +00:00
}
/**
* Edit some properties of Task
* @param mixed $id
* @return void
2022-07-10 12:23:53 +00:00
* @throws Exception
*/
function putId($id)
{
checkWriteAccess();
$id = (int)$id;
2023-03-24 15:23:06 +00:00
if (!DBCore::default()->taskExists($id)) {
$this->response->data = ['total' => 0];
return;
}
2022-07-10 12:23:53 +00:00
$action = $this->req->jsonBody['action'] ?? '';
switch ($action) {
case 'edit': $this->response->data = $this->editTask($id); break;
case 'complete': $this->response->data = $this->completeTask($id); break;
case 'note': $this->response->data = $this->editNote($id); break;
case 'move': $this->response->data = $this->moveTask($id); break;
case 'priority': $this->response->data = $this->priorityTask($id); break;
default: $this->response->data = ['total' => 0];
2022-07-10 12:23:53 +00:00
}
}
/**
* Parse task input string to components for representing in edit/add form
* @return void
2022-07-10 12:23:53 +00:00
* @throws Exception
*/
function postTitleParse()
{
checkWriteAccess();
$t = array(
'title' => trim( $this->req->jsonBody['title'] ?? '' ),
'prio' => 0,
'tags' => '',
'duedate' => '',
2022-07-10 12:23:53 +00:00
);
if (Config::get('smartsyntax') != 0 && (false !== $a = parseSmartSyntax($t['title'])))
2022-07-10 12:23:53 +00:00
{
$t['title'] = (string) ($a['title'] ?? '');
$t['prio'] = (int) ($a['prio'] ?? 0);
$t['tags'] = (string) ($a['tags'] ?? '');
if (isset($a['duedate']) && $a['duedate'] != '') {
$dueA = $this->prepareDuedate($a['duedate']);
$t['duedate'] = $dueA['formatted'];
}
2022-07-10 12:23:53 +00:00
}
$this->response->data = $t;
2022-07-10 12:23:53 +00:00
}
function postNewCounter()
{
checkReadAccess();
$lists = $this->req->jsonBody['lists'] ?? [];
if (!is_array($lists)) $lists = [];
$userLists = []; // [string]
if (!haveWriteAccess()) {
$userLists = $this->getUserListsSimple(true);
if ($userLists) {
$sqlWhereList = "AND list_id IN (". implode(',', $userLists). ")";
// remove lists without access granted
$lists = array_filter($lists, function($item) use ($userLists) {
return in_array( (string)($item['listId'] ?? ''), $userLists );
});
}
}
$sqlWhereList = [];
foreach ($lists as $item) {
$later = (int) ($item['later'] ?? 0);
$sqlWhereList[] = "(list_id = ". (int)$item['listId']. " AND compl=0 AND d_created > $later)";
}
$db = DBConnection::instance();
$a = [];
if ($sqlWhereList) {
$sqlWhere = implode(' OR ', $sqlWhereList);
$q = $db->dq("SELECT list_id, COUNT(id) c FROM {$db->prefix}todolist
WHERE $sqlWhere GROUP BY list_id");
while ($r = $q->fetchAssoc()) {
$a[] = [
'listId' => (int)$r['list_id'],
'counter' => (int)$r['c'],
];
}
}
$b = [];
$list = (int) ($this->req->jsonBody['list'] ?? 0);
$later = (int) ($this->req->jsonBody['later'] ?? 0);
if ($list > 0 && $later > 0 && (!$userLists || in_array((string)$list, $userLists))) {
$q = $db->dq("SELECT id FROM {$db->prefix}todolist
WHERE list_id = $list AND compl=0 AND d_created > $later");
while ($r = $q->fetchAssoc()) {
$b[] = (int)$r['id'];
}
}
$this->response->data = [
'total' => count($b) + count($a),
'tasks' => $b,
'lists' => $a
];
}
2022-07-10 12:23:53 +00:00
/* Private Functions */
private function newTaskInList(int $listId): ?array
2022-07-10 12:23:53 +00:00
{
$db = DBConnection::instance();
$t = array();
$t['total'] = 0;
$title = trim($this->req->jsonBody['title'] ?? '');
$prio = 0;
$tags = '';
$duedate = null;
2022-07-10 12:23:53 +00:00
if (Config::get('smartsyntax') != 0)
{
$a = parseSmartSyntax($title);
2022-07-10 12:23:53 +00:00
if ($a === false) {
return $t;
}
$title = (string)$a['title'];
$prio = (int)$a['prio'];
$tags = (string)$a['tags'];
if (isset($a['duedate']) && preg_match("|^\d+-\d+-\d+$|", $a['duedate'])) {
$duedate = $a['duedate'];
}
2022-07-10 12:23:53 +00:00
}
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");
$date = time();
2022-07-10 12:23:53 +00:00
$db->ex("BEGIN");
$db->dq("INSERT INTO {$db->prefix}todolist (uuid,list_id,title,d_created,d_edited,ow,prio,duedate) VALUES (?,?,?,?,?,?,?,?)",
array(generateUUID(), $listId, $title, $date, $date, $ow, $prio, $duedate) );
2022-07-10 12:23:53 +00:00
$id = (int) $db->lastInsertId();
if ($tags != '')
{
$aTags = $this->prepareTags($tags);
if ($aTags) {
$this->addTaskTags($id, $aTags['ids'], $listId);
}
}
$db->ex("COMMIT");
$task = $this->getTaskRowById($id);
MTTNotificationCenter::postNotification(MTTNotification::didCreateTask, $task);
$t['list'][] = $task;
2022-07-10 12:23:53 +00:00
$t['total'] = 1;
return $t;
}
private function fullNewTaskInList(int $listId): ?array
2022-07-10 12:23:53 +00:00
{
$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 = MTTSmartSyntax::parseDuedate(trim( $this->req->jsonBody['duedate'] ?? '' ));
2022-07-10 12:23:53 +00:00
$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");
$date = time();
2022-07-10 12:23:53 +00:00
$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, $date, $date, $ow, $prio, $note, $duedate) );
2022-07-10 12:23:53 +00:00
$id = (int) $db->lastInsertId();
if ($tags != '')
{
$aTags = $this->prepareTags($tags);
if ($aTags) {
$this->addTaskTags($id, $aTags['ids'], $listId);
}
}
$db->ex("COMMIT");
$task = $this->getTaskRowById($id);
MTTNotificationCenter::postNotification(MTTNotification::didCreateTask, $task);
$t['list'][] = $task;
2022-07-10 12:23:53 +00:00
$t['total'] = 1;
return $t;
}
private function editTask(int $id): ?array
2022-07-10 12:23:53 +00:00
{
$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 = MTTSmartSyntax::parseDuedate(trim( $this->req->jsonBody['duedate'] ?? '' ));
2022-07-10 12:23:53 +00:00
$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) {
2022-07-10 12:23:53 +00:00
$this->addTaskTags($id, $aTags['ids'], $listId);
}
$db->dq("UPDATE {$db->prefix}todolist SET title=?,note=?,prio=?,duedate=?,d_edited=? WHERE id=$id",
array($title, $note, $prio, $duedate, time()) );
2022-07-10 12:23:53 +00:00
$db->ex("COMMIT");
$task = $this->getTaskRowById($id);
MTTNotificationCenter::postNotification(MTTNotification::didEditTask, ['task' => $task]);
$t['list'][] = $task;
$t['total'] = 1;
2022-07-10 12:23:53 +00:00
return $t;
}
private function moveTask(int $id): ?array
2022-07-10 12:23:53 +00:00
{
$fromId = (int)($this->req->jsonBody['from'] ?? 0);
$toId = (int)($this->req->jsonBody['to'] ?? 0);
$result = $this->doMoveTask($id, $toId);
if ($result && MTTNotificationCenter::hasObserversForNotification(MTTNotification::didEditTask)) {
$task = $this->getTaskRowById($id);
MTTNotificationCenter::postNotification(MTTNotification::didEditTask, [
'property' => 'list',
'task' => $task
]);
}
2022-07-10 12:23:53 +00:00
$t = array('total' => $result ? 1 : 0);
if ($fromId == -1 && $result) {
$t['list'][] = $this->getTaskRowById($id);
2022-07-10 12:23:53 +00:00
}
return $t;
}
private function doMoveTask(int $id, int $listId): bool
2022-07-10 12:23:53 +00:00
{
$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
2022-08-27 18:07:31 +00:00
if (!$db->sq("SELECT COUNT(*) FROM {$db->prefix}lists WHERE id=?", [$listId]))
2022-07-10 12:23:53 +00:00
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): ?array
2022-07-10 12:23:53 +00:00
{
$db = DBConnection::instance();
$compl = (int)($this->req->jsonBody['compl'] ?? 0);
$listId = (int)$db->sq("SELECT list_id FROM {$db->prefix}todolist WHERE id=$id");
2022-07-10 12:35:19 +00:00
if ($compl) $ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}todolist WHERE list_id=$listId AND compl=1");
2022-07-10 12:23:53 +00:00
else $ow = 1 + (int)$db->sq("SELECT MAX(ow) FROM {$db->prefix}todolist WHERE list_id=$listId AND compl=0");
$date = time();
$dateCompleted = $compl ? $date : 0;
2022-07-10 12:23:53 +00:00
$db->dq("UPDATE {$db->prefix}todolist SET compl=$compl,ow=$ow,d_completed=?,d_edited=? WHERE id=$id",
array($dateCompleted, $date) );
$task = $this->getTaskRowById($id);
MTTNotificationCenter::postNotification(MTTNotification::didCompleteTask, $task);
2022-07-10 12:23:53 +00:00
$t = array();
$t['total'] = 1;
$t['list'][] = $task;
2022-07-10 12:23:53 +00:00
return $t;
}
private function editNote(int $id): ?array
2022-07-10 12:23:53 +00:00
{
$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()) );
if (MTTNotificationCenter::hasObserversForNotification(MTTNotification::didEditTask)) {
$task = $this->getTaskRowById($id);
MTTNotificationCenter::postNotification(MTTNotification::didEditTask, [
'property' => 'note',
'task' => $task
]);
}
2022-07-10 12:23:53 +00:00
$t = array();
$t['total'] = 1;
$t['list'][] = array('id'=>$id, 'note'=> noteMarkup($note), 'noteText'=>(string)$note);
return $t;
}
private function priorityTask(int $id): ?array
2022-07-10 12:23:53 +00:00
{
$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()) );
if (MTTNotificationCenter::hasObserversForNotification(MTTNotification::didEditTask)) {
$task = $this->getTaskRowById($id);
MTTNotificationCenter::postNotification(MTTNotification::didEditTask, [
'property' => 'priority',
'task' => $task
]);
}
2022-07-10 12:23:53 +00:00
$t = array();
$t['total'] = 1;
$t['list'][] = array('id'=>$id, 'prio'=>$prio);
return $t;
}
private function changeTaskOrder(): ?array
2022-07-10 12:23:53 +00:00
{
$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(bool $readOnly = false): array
2022-07-10 12:23:53 +00:00
{
$db = DBConnection::instance();
$sqlWhere = '';
if ($readOnly) {
$sqlWhere = "WHERE published=1";
}
2022-07-10 12:23:53 +00:00
$a = array();
$q = $db->dq("SELECT id,name FROM {$db->prefix}lists $sqlWhere ORDER BY id ASC");
2022-07-10 12:23:53 +00:00
while($r = $q->fetchRow()) {
$a[ (string)$r[0] ] = (string)$r[1];
2022-07-10 12:23:53 +00:00
}
return $a;
}
private function getTaskRowById(int $id): ?array
{
2023-03-24 15:23:06 +00:00
$r = DBCore::default()->getTaskById($id);
if (!$r) {
throw new Exception("Failed to fetch task data");
}
return $this->prepareTaskRow($r);
}
private function prepareTaskRow(array $r): array
2022-07-10 12:23:53 +00:00
{
$lang = Lang::instance();
$dueA = $this->prepareDuedate($r['duedate']);
$dCreated = timestampToDatetime($r['d_created']);
$isEdited = ($r['d_edited'] != $r['d_created']);
$dEdited = $isEdited ? timestampToDatetime($r['d_edited']) : '';
2022-07-10 12:23:53 +00:00
$dCompleted = $r['d_completed'] ? timestampToDatetime($r['d_completed']) : '';
if (!Config::get('showtime')) {
$dCreatedFull = timestampToDatetime($r['d_created'], true);
$dEditedFull = $isEdited ? timestampToDatetime($r['d_edited'], true) : '';
$dCompletedFull = $r['d_completed'] ? timestampToDatetime($r['d_completed'], true) : '';
}
else {
$dCreatedFull = $dCreated;
$dEditedFull = $dEdited;
$dCompletedFull = $dCompleted;
}
2022-07-10 12:23:53 +00:00
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'],
'dateFull' => htmlarray($dCreatedFull),
'dateInlineTitle' => htmlarray(sprintf($lang->get('taskdate_inline_created'), $dCreated)), //TODO: move preparing of *inlineTitle to js
'dateEdited' => htmlarray($dEdited),
2022-07-10 12:23:53 +00:00
'dateEditedInt' => (int)$r['d_edited'],
'dateEditedFull' => htmlarray($dEditedFull),
'dateEditedInlineTitle' => htmlarray(sprintf($lang->get('taskdate_inline_edited'), $dEdited)),
'isEdited' => (bool)$isEdited,
2022-07-10 12:23:53 +00:00
'dateCompleted' => htmlarray($dCompleted),
'dateCompletedFull' => htmlarray($dCompletedFull),
2022-07-10 12:23:53 +00:00
'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' => htmlarray($dueA['formatted']),
2022-07-10 12:23:53 +00:00
'dueClass' => $dueA['class'],
'dueStr' => htmlarray($dueA['str']),
2022-07-10 12:23:53 +00:00
'dueInt' => $this->date2int($r['duedate']),
'dueTitle' => htmlarray(sprintf($lang->get('taskdate_inline_duedate'), $dueA['formattedlong'])),
);
}
private function prepareDuedate($duedate): array
2022-07-10 12:23:53 +00:00
{
$lang = Lang::instance();
$a = array( 'class'=>'', 'str'=>'', 'formatted'=>'', 'formattedlong'=>'', 'timestamp'=>0 );
if ($duedate == '') {
return $a;
}
$ad = explode('-', $duedate);
$y = (int)$ad[0];
$m = (int)$ad[1];
$d = (int)$ad[2];
$a['timestamp'] = mktime(0, 0, 0, $m, $d, $y);
$oToday = new DateTimeImmutable(date("Y-m-d"));
$oDue = new DateTimeImmutable($duedate);
$oDiff = $oToday->diff($oDue);
if ($oDiff === false) {
return $a;
}
$thisYear = ((int)$oToday->format('Y') == $y);
$days = $oDiff->days;
if ($oDiff->invert) $days *= -1;
$exact = Config::get('exactduedate') ? true : false;
if ($days < -7 && !$thisYear) {
$a['class'] = 'past';
$a['str'] = formatDate3(Config::get('dateformat2'), $y, $m, $d, $lang);
}
elseif ($days < -7) {
$a['class'] = 'past';
$a['str'] = formatDate3(Config::get('dateformatshort'), $y, $m, $d, $lang);
}
elseif ($days < -1) {
$a['class'] = 'past';
$a['str'] = !$exact ? sprintf($lang->get('daysago'), abs($days)) : formatDate3(Config::get('dateformatshort'), $y, $m, $d, $lang);
}
elseif ($days == -1) {
$a['class'] = 'past';
$a['str'] = !$exact ? $lang->get('yesterday') : formatDate3(Config::get('dateformatshort'), $y, $m, $d, $lang);
}
elseif ($days == 0) {
$a['class'] = 'today';
$a['str'] = !$exact ? $lang->get('today') : formatDate3(Config::get('dateformatshort'), $y, $m, $d, $lang);
}
elseif ($days == 1) {
$a['class'] = 'today';
$a['str'] = !$exact ? $lang->get('tomorrow') : formatDate3(Config::get('dateformatshort'), $y, $m, $d, $lang);
}
elseif ($days <= 7) {
$a['class'] = 'soon';
$a['str'] = !$exact ? sprintf($lang->get('indays'), $days) : formatDate3(Config::get('dateformatshort'), $y, $m, $d, $lang);
}
elseif ($thisYear) {
$a['class'] = 'future';
$a['str'] = formatDate3(Config::get('dateformatshort'), $y, $m, $d, $lang);
}
else {
$a['class'] = 'future';
$a['str'] = formatDate3(Config::get('dateformat2'), $y, $m, $d, $lang);
}
2022-07-10 12:23:53 +00:00
#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) : int
2022-07-10 12:23:53 +00:00
{
if (!$d) {
return 33330000;
}
2022-07-10 12:23:53 +00:00
$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 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): array
2022-07-10 12:23:53 +00:00
{
$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): ?array
2022-07-10 12:23:53 +00:00
{
$tags = explode(',', $tagsStr);
if (!$tags) return null;
2022-07-10 12:23:53 +00:00
$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)
);
}
}
2022-07-10 12:23:53 +00:00
}