mytinytodo/src/includes/api/ListsController.php

392 lines
13 KiB
PHP

<?php declare(strict_types=1);
/*
This file is a part of myTinyTodo.
(C) Copyright 2022-2023 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 void
* @throws Exception
*/
function get()
{
$db = DBConnection::instance();
check_token();
$t = array();
$t['total'] = 0;
$haveWriteAccess = haveWriteAccess();
if (!$haveWriteAccess) {
$sqlWhere = 'WHERE published=1';
}
else {
$sqlWhere = '';
$t['list'][] = $this->prepareAllTasksList(); // show alltasks lists only for authorized user
$t['total'] = 1;
}
$t['time'] = time();
$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, $haveWriteAccess);
}
$this->response->data = $t;
}
/**
* Create new list and Actions with all lists
* Code 201 on success
* @return void
* @throws Exception
*/
function post()
{
checkWriteAccess();
$action = $this->req->jsonBody['action'] ?? '';
switch ($action) {
case 'order': $this->response->data = $this->changeListOrder(); break; //compatibility
case 'new':
default: $this->response->data = $this->createList();
}
}
/**
* Actions with all lists
* @return void
* @throws Exception
*/
function put()
{
checkWriteAccess();
$action = $this->req->jsonBody['action'] ?? '';
switch ($action) {
case 'order': $this->response->data = $this->changeListOrder(); break;
default: $this->response->data = ['total' => 0]; // error 400 ?
}
}
/* Single list */
/**
* Get single list by Id
* @param mixed $id
* @return void
* @throws Exception
*/
function getId($id)
{
checkReadAccess($id);
$db = DBConnection::instance();
$r = $db->sqa( "SELECT * FROM {$db->prefix}lists WHERE id=?", array($id) );
if (!$r) {
$this->response->data = null;
return;
}
$t = $this->prepareList($r, haveWriteAccess());
$this->response->data = $t;
}
/**
* Delete list by Id
* @param mixed $id
* @return void
* @throws Exception
*/
function deleteId($id)
{
checkWriteAccess();
$this->response->data = $this->deleteList((int)$id);
}
/**
* Edit some properties of List
* Actions: rename, ...
* @param mixed $id
* @return void
* @throws Exception
*/
function putId($id)
{
checkWriteAccess();
$id = (int)$id;
$action = $this->req->jsonBody['action'] ?? '';
switch ($action) {
case 'rename': $this->response->data = $this->renameList($id); break;
case 'sort': $this->response->data = $this->sortList($id); break;
case 'publish': $this->response->data = $this->publishList($id); break;
case 'enableFeedKey': $this->response->data = $this->enableFeedKey($id); break;
case 'showNotes': $this->response->data = $this->showNotes($id); break;
case 'hide': $this->response->data = $this->hideList($id); break;
case 'clearCompleted': $this->response->data = $this->clearCompleted($id); break;
case 'delete': $this->response->data = $this->deleteList($id); break; //compatibility
default: $this->response->data = ['total' => 0];
}
}
/* Private Functions */
private function prepareAllTasksList(): array
{
//default values
$hidden = 1;
$sort = 3;
$showCompleted = 1;
$opts = Config::requestDomain('alltasks.json');
if ( isset($opts['hidden']) ) $hidden = (int)$opts['hidden'] ? 1 : 0;
if ( isset($opts['sort']) ) $sort = (int)$opts['sort'];
if ( isset($opts['showCompleted']) ) $showCompleted = (int)$opts['showCompleted'];
return array(
'id' => -1,
'name' => htmlarray(__('alltasks')),
'sort' => $sort,
'published' => 0,
'showCompl' => $showCompleted,
'showNotes' => 0,
'hidden' => $hidden,
'feedKey' => '',
);
}
private function getListRowById(int $id)
{
$r = DBCore::default()->getListById($id);
if (!$r) {
throw new Exception("Failed to fetch list data");
}
return $this->prepareList($r, true);
}
private function prepareList($row, bool $haveWriteAccess): array
{
$taskview = (int)$row['taskview'];
$feedKey = '';
if ($haveWriteAccess) {
$extra = json_decode($row['extra'] ?? '', true, 10, JSON_INVALID_UTF8_SUBSTITUTE);
if ($extra === false) {
error_log("Failed to decodes JSON data of list extra listId=". (int)$row['id'] . ": " . json_last_error_msg());
$extra = [];
}
$feedKey = (string) ($extra['feedKey'] ?? '');
}
return array(
'id' => $row['id'],
'name' => htmlarray($row['name']),
'sort' => (int)$row['sorting'],
'published' => $row['published'] ? 1 :0,
'showCompl' => $taskview & 1 ? 1 : 0,
'showNotes' => $taskview & 2 ? 1 : 0,
'hidden' => $taskview & 4 ? 1 : 0,
'feedKey' => $feedKey,
);
}
private function createList(): ?array
{
$t = array();
$t['total'] = 0;
$id = DBCore::default()->createListWithName($this->req->jsonBody['name'] ?? '');
if (!$id) {
return $t;
}
$db = DBConnection::instance();
$t['total'] = 1;
$r = $db->sqa("SELECT * FROM {$db->prefix}lists WHERE id=$id");
$oo = $this->prepareList($r, true);
MTTNotificationCenter::postNotification(MTTNotification::didCreateList, $oo);
$t['list'][] = $oo;
return $t;
}
private function renameList(int $id): ?array
{
$db = DBConnection::instance();
$t = array();
$t['total'] = 0;
$name = trim($this->req->jsonBody['name'] ?? '');
if ($name == '')
return $t;
$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, true);
return $t;
}
private function sortList(int $listId): ?array
{
$sort = (int)($this->req->jsonBody['sort'] ?? 0);
self::setListSortingById($listId, $sort);
return ['total'=>1];
}
static function setListSortingById(int $listId, int $sort)
{
$db = DBConnection::instance();
if ($sort < 0 || ($sort > 5 && $sort < 100) || $sort > 105) {
$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()));
}
}
static function setListShowCompletedById(int $listId, bool $showCompleted)
{
$db = DBConnection::instance();
if ($listId == -1) {
$opts = Config::requestDomain('alltasks.json');
$opts['showCompleted'] = (int)$showCompleted;
Config::saveDomain('alltasks.json', $opts);
}
else {
$bitwise = $showCompleted ? 'taskview | 1' : 'taskview & ~1';
$db->dq("UPDATE {$db->prefix}lists SET taskview=$bitwise WHERE id=?", [$listId]);
}
}
private function publishList(int $listId): ?array
{
$db = DBConnection::instance();
$publish = (int)($this->req->jsonBody['publish'] ?? 0);
$db->ex("UPDATE {$db->prefix}lists SET published=?,d_edited=? WHERE id=$listId", array($publish ? 1 : 0, time()));
return ['total'=>1];
}
private function enableFeedKey(int $listId): ?array
{
$db = DBConnection::instance();
$flag = (int)($this->req->jsonBody['enable'] ?? 0);
$json = $db->sq("SELECT extra FROM {$db->prefix}lists WHERE id=$listId") ?? '';
$extra = strlen($json) > 0 ? json_decode($json, true, 10, JSON_INVALID_UTF8_SUBSTITUTE) : [];
if ($extra === false) {
error_log("Failed to decodes JSON data of list extra listId=$listId: " . json_last_error_msg());
$extra = [];
}
if ($flag == 0) {
$extra['feedKey'] = '';
}
else {
$extra['feedKey'] = randomString();
}
$json = json_encode($extra, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$db->ex("UPDATE {$db->prefix}lists SET extra=?,d_edited=? WHERE id=$listId", array($json, time()));
return [
'total' => 1,
'list' => [[
'id' => $listId,
'feedKey' => $extra['feedKey']
]]
];
}
private function showNotes(int $listId): ?array
{
$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): ?array
{
$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): ?array
{
$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");
if (MTTNotificationCenter::hasObserversForNotification(MTTNotification::didDeleteCompletedInList)) {
$list = $this->getListRowById($listId);
MTTNotificationCenter::postNotification(MTTNotification::didDeleteCompletedInList, [
'total' => $t['total'],
'list' => $list
]);
}
return $t;
}
private function changeListOrder(): ?array
{
$t = array();
$t['total'] = 0;
$order = $this->req->jsonBody['order'] ?? [];
if (!array_is_list($order)) {
return $t;
}
$db = DBConnection::instance();
$a = array();
$setCase = '';
$max = count($order);
for ($i = 0; $i < $max; $i++) {
$id = (int)$order[$i];
$a[] = $id;
$setCase .= "WHEN id=$id THEN $i\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;
}
private function deleteList(int $id)
{
$db = DBConnection::instance();
$t = array();
$t['total'] = 0;
$id = (int)$id;
$list = null;
if (MTTNotificationCenter::hasObserversForNotification(MTTNotification::didDeleteList)) {
$list = $this->getListRowById($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");
if ($t['total'] && MTTNotificationCenter::hasObserversForNotification(MTTNotification::didDeleteList)) {
MTTNotificationCenter::postNotification(MTTNotification::didDeleteList, $list);
}
return $t;
}
}