set_charset('utf8mb4'); header("content-type: application/json"); header("cache-control: no-store"); switch ($_REQUEST['api']) { case 'getBoards': getBoards(); break; case 'getBoard': getBoard(); break; case 'newBoard': newBoard(); break; case 'editBoard': editBoard(); break; case 'addList': addList(); break; case 'renameList': renameList(); break; case 'moveList': moveList(); break; case 'newCard': newCard(); break; case 'addTag': addTag(); break; case 'getCard': getCard(); break; case 'saveCard': saveCard(); break; case 'moveCard': moveCard(); break; case 'deleteList': deleteList(); break; case 'delTag': delTag(); break; case 'cardColor': cardColor(); break; case 'archiveCard': archiveCard(); break; case 'deleteBoard': deleteBoard(); break; case 'export': export(); break; case 'import': import(); break; default: http_response_code(400); } $scon->close(); function getBoards() { //Get Boards global $scon; $res = $scon->query('SELECT * FROM boards'); if(!empty($res) && mysqli_num_rows($res) > 0) { $rows = mysqli_fetch_all($res, MYSQLI_ASSOC); echo json_encode($rows); } else echo '[]'; } function getCard() { //Get Card global $scon; $res = $scon->query('SELECT date_format(cdate,"%e %b %Y") AS cdate,date_format(mdate,"%e %b %Y") AS mdate,description FROM cards WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['listid'].'" AND id="'.$_REQUEST['cardid'].'" LIMIT 1'); if(!empty($res) && mysqli_num_rows($res) === 1) { $row = mysqli_fetch_assoc($res); echo json_encode($row); } else http_response_code(404); } function getBoard() { //Get Board (lists + cards) global $scon; $res = $scon->query('SELECT 1 FROM boards WHERE id="'.$_REQUEST['bid'].'"'); if(!$res || !mysqli_num_rows($res)) { http_response_code(404); return; } $resL = $scon->query('SELECT id,ordr,name,0 as "start",0 as "end" FROM lists WHERE board="'.$_REQUEST['bid'].'" ORDER BY ordr asc'); $resC = $scon->query('SELECT list,id,parent,title,tags,color,IF(description IS NULL,null,1) AS description FROM cards WHERE board="'.$_REQUEST['bid'].'"'); $rowsL = mysqli_fetch_all($resL, MYSQLI_ASSOC); $rowsC = mysqli_fetch_all($resC, MYSQLI_ASSOC); $res = new stdClass(); $res->lists = $rowsL; $res->cards = $rowsC; echo json_encode($res); } function newBoard() { //New Board global $scon; $bid = idGen32(); $bgimg = $_REQUEST['imgurl'] ?: null; $sq = $scon->prepare('INSERT INTO boards(id,name,bgimg) VALUES(?,?,?)'); $sq->bind_param('sss', $bid, $_REQUEST['name'], $bgimg); $sq->execute(); $sq->close(); if($sq) echo $bid; else http_response_code(500); } function editBoard() { //Edit Board global $scon; $name = $_REQUEST['name']; $sq = $scon->prepare('UPDATE boards SET name=?,bgimg=? WHERE id=?'); $sq->bind_param('sss', $name, $_REQUEST['imgurl'], $_REQUEST['bid']); $sq->execute(); $sq->close(); if(!$sq) http_response_code(500); } function addList() { //Add List global $scon; $lid = idGen32(); //idempotency check $res = $scon->query('SELECT 1 FROM lists WHERE board="'.$_REQUEST['bid'].'" AND ordr >= '.$_REQUEST['pos'].' LIMIT 1'); if(mysqli_num_rows($res) !== 0) {http_response_code(409); return;} $sq = $scon->prepare('INSERT INTO lists(board,id,ordr,name) VALUES("'.$_REQUEST['bid'].'","'.$lid.'",'.$_REQUEST['pos'].',?)'); $sq->bind_param('s', $_REQUEST['name']); $sq->execute(); $sq->close(); if($sq) echo $lid; else http_response_code(500); } function renameList() { //Rename List global $scon; $sq = $scon->prepare('UPDATE lists SET name=? WHERE board="'.$_REQUEST['bid'].'" AND id="'.$_REQUEST['listid'].'" LIMIT 1'); $sq->bind_param('s', $_REQUEST['title']); $sq->execute(); $sq->close(); if(!$sq) http_response_code(500); } function moveList() { //Move List global $scon; //idempotency check $res = $scon->query('SELECT 1 FROM lists WHERE board="'.$_REQUEST['bid'].'" AND id="'.$_REQUEST['lid1'].'" AND ordr='.$_REQUEST['ordr1'].' LIMIT 1'); if(mysqli_num_rows($res) !== 1) {http_response_code(409); return;} $res = $scon->query('SELECT 1 FROM lists WHERE board="'.$_REQUEST['bid'].'" AND id="'.$_REQUEST['lid2'].'" AND ordr='.$_REQUEST['ordr2'].' LIMIT 1'); if(mysqli_num_rows($res) !== 1) {http_response_code(409); return;} $res = $scon->query('UPDATE lists SET ordr="'.$_REQUEST['ordr2'].'" WHERE board="'.$_REQUEST['bid'].'" AND id="'.$_REQUEST['lid1'].'" LIMIT 1'); $res = $scon->query('UPDATE lists SET ordr="'.$_REQUEST['ordr1'].'" WHERE board="'.$_REQUEST['bid'].'" AND id="'.$_REQUEST['lid2'].'" LIMIT 1'); if(!$res) http_response_code(500); } function newCard() { //Add Card global $scon; $cardid = idGen32(); //idempotency check if($_REQUEST['pid'] === '0') { //only card in list $res = $scon->query('SELECT 1 FROM lists WHERE board="'.$_REQUEST['bid'].'" AND id="'.$_REQUEST['listid'].'" LIMIT 1'); if(mysqli_num_rows($res) !== 1) {http_response_code(409); return;} $res = $scon->query('SELECT 1 FROM cards WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['listid'].'" LIMIT 1'); if(mysqli_num_rows($res) !== 0) {http_response_code(409); return;} } else { $res = $scon->query('SELECT 1 FROM cards WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['listid'].'" AND id="'.$_REQUEST['pid'].'" LIMIT 1'); if(mysqli_num_rows($res) !== 1) {http_response_code(409); return;} $res = $scon->query('SELECT 1 FROM cards WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['listid'].'" AND parent="'.$_REQUEST['pid'].'" LIMIT 1'); if(mysqli_num_rows($res) !== 0) {http_response_code(409); return;} } $res = $scon->query('INSERT INTO cards(board,list,id,parent,title,tags,description) VALUES("'.$_REQUEST['bid'].'","'.$_REQUEST['listid'].'","'.$cardid.'","'.$_REQUEST['pid'].'","'.$_REQUEST['title'].'",null,null)'); if($res) echo $cardid; else http_response_code(500); } function addTag() { //Add Tag global $scon; $color = $_REQUEST['color']; if(preg_match("/^[a-zA-Z0-9#]{3,7}$/", $color)) { $res = $scon->query('UPDATE cards SET tags=CONCAT_WS(" ", tags, "'.$color.'") WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['listid'].'" AND id="'.$_REQUEST['cardid'].'" LIMIT 1'); if(!$res) http_response_code(500); } else http_response_code(400); } function saveCard() { //Save Card global $scon; if($_SERVER['REQUEST_METHOD'] !== 'POST') {http_response_code(400); return;} $desc = file_get_contents('php://input') ?: null; $sq = $scon->prepare('UPDATE cards SET title=?,description=? WHERE board=? AND list=? AND id=? LIMIT 1'); $sq->bind_param('sssss', $_REQUEST['title'], $desc, $_REQUEST['bid'], $_REQUEST['listid'], $_REQUEST['cardid']); $sq->execute(); $sq->close(); if(!$sq) http_response_code(500); } function moveCard() { //Move Card global $scon; if($_REQUEST['stid'] === $_REQUEST['dpid'] || $_REQUEST['stid'] === $_REQUEST['dtid']) { http_response_code(400); return; } //idempotency check //source $res = $scon->query('SELECT parent FROM cards WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['slid'].'" AND id="'.$_REQUEST['stid'].'" LIMIT 1'); if(mysqli_num_rows($res) !== 1) {http_response_code(409); return;} $parent = mysqli_fetch_array($res)[0]; if($parent !== $_REQUEST['spid']) {http_response_code(409); return;} //void if($_REQUEST['vtid'] !== '0') { //src not last in list $res = $scon->query('SELECT parent FROM cards WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['slid'].'" AND id="'.$_REQUEST['vtid'].'" LIMIT 1'); if(mysqli_num_rows($res) !== 1) {http_response_code(409); return;} $parent = mysqli_fetch_array($res)[0]; if($parent !== $_REQUEST['stid']) {http_response_code(409); return;} } else { //src is last in list $res = $scon->query('SELECT 1 FROM cards WHERE board="'.$_REQUEST['bid'].'" AND parent="'.$_REQUEST['stid'].'" LIMIT 1'); if(mysqli_num_rows($res) !== 0) {http_response_code(409); return;} } //dest if($_REQUEST['dtid'] === '0') { //dest last in list if($_REQUEST['dpid'] !== '0') { $res = $scon->query('SELECT 1 FROM cards WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['dlid'].'" AND id="'.$_REQUEST['dpid'].'" LIMIT 1'); if(mysqli_num_rows($res) !== 1) {http_response_code(409); return;} $res = $scon->query('SELECT 1 FROM cards WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['dlid'].'" AND parent="'.$_REQUEST['dpid'].'" LIMIT 1'); if(mysqli_num_rows($res) !== 0) {http_response_code(409); return;} } else { //dest only in list $res = $scon->query('SELECT 1 FROM lists WHERE board="'.$_REQUEST['bid'].'" AND id="'.$_REQUEST['dlid'].'" LIMIT 1'); if(mysqli_num_rows($res) !== 1) {http_response_code(409); return;} } } else { //dest not last in list $res = $scon->query('SELECT parent FROM cards WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['dlid'].'" AND id="'.$_REQUEST['dtid'].'" LIMIT 1'); if(mysqli_num_rows($res) !== 1) {http_response_code(409); return;} $parent = mysqli_fetch_array($res)[0]; if($parent !== $_REQUEST['dpid']) {http_response_code(409); return;} } //set src pid to dest pid $scon->query('UPDATE cards SET parent="'.$_REQUEST['dpid'].'",list="'.$_REQUEST['dlid'].'" WHERE board="'.$_REQUEST['bid'].'" AND id="'.$_REQUEST['stid'].'" LIMIT 1'); //set dest pid to src id if($_REQUEST['dtid'] !== '0') $scon->query('UPDATE cards SET parent="'.$_REQUEST['stid'].'" WHERE board="'.$_REQUEST['bid'].'" AND id="'.$_REQUEST['dtid'].'" LIMIT 1'); //set void pid to src pid if($_REQUEST['vtid'] !== '0') $scon->query('UPDATE cards SET parent="'.$_REQUEST['spid'].'" WHERE board="'.$_REQUEST['bid'].'" AND id="'.$_REQUEST['vtid'].'" LIMIT 1'); } function deleteList() { //Delete List global $scon; if($_SERVER['REQUEST_METHOD'] !== 'PUT') {http_response_code(400); return;} $res = $scon->query('INSERT INTO archive SELECT * FROM cards WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['listid'].'"'); $res = $scon->query('DELETE FROM cards WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['listid'].'"'); $res = $scon->query('DELETE FROM lists WHERE board="'.$_REQUEST['bid'].'" AND id="'.$_REQUEST['listid'].'"'); if(!$res) http_response_code(500); } function delTag() { //Delete Tag global $scon; $color = $_REQUEST['color']; $res = $scon->query('SELECT tags FROM cards WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['listid'].'" AND id="'.$_REQUEST['cardid'].'" LIMIT 1'); if(empty($res) || mysqli_num_rows($res) !== 1) { http_response_code(404); return; } $tag = mysqli_fetch_array($res)[0]; $tag = preg_replace("/\B$color\b\s?/", '', $tag); $tag = trim($tag); $tag = empty($tag) ? 'null' : '"'.$tag.'"'; $res = $scon->query('UPDATE cards SET tags='.$tag.' WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['listid'].'" AND id="'.$_REQUEST['cardid'].'" LIMIT 1'); if(!$res) http_response_code(500); } function cardColor() { //Card Color global $scon; $color = (empty($_REQUEST['color']) || $_REQUEST['color'] === '#f0f0f0') ? 'null' : '"'.$_REQUEST['color'].'"'; $res = $scon->query('UPDATE cards SET color='.$color.' WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['listid'].'" AND id="'.$_REQUEST['cardid'].'" LIMIT 1'); if(!$res) http_response_code(500); } function archiveCard() { //Archive Card global $scon; $res = $scon->query('INSERT INTO archive SELECT * FROM cards WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['listid'].'" AND id="'.$_REQUEST['cardid'].'" LIMIT 1'); $res = $scon->query('UPDATE cards SET parent="'.$_REQUEST['pid'].'" WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['listid'].'" AND parent="'.$_REQUEST['cardid'].'" LIMIT 1'); $res = $scon->query('DELETE FROM cards WHERE board="'.$_REQUEST['bid'].'" AND list="'.$_REQUEST['listid'].'" AND id="'.$_REQUEST['cardid'].'" LIMIT 1'); if(!$res) http_response_code(500); } function deleteBoard() { //Delete Board global $scon; if($_SERVER['REQUEST_METHOD'] !== 'PUT') {http_response_code(400); return;} $scon->query('INSERT INTO archive SELECT * FROM cards WHERE board="'.$_REQUEST['bid'].'"'); $scon->query('DELETE FROM cards WHERE board="'.$_REQUEST['bid'].'"'); $scon->query('DELETE FROM lists WHERE board="'.$_REQUEST['bid'].'"'); $res = $scon->query('DELETE FROM boards WHERE id="'.$_REQUEST['bid'].'"'); if(!$res) http_response_code(404); } function export() { //Export Board global $scon; $res = $scon->query('SELECT * FROM boards WHERE id="'.$_REQUEST['bid'].'"'); if(empty($res) || mysqli_num_rows($res) !== 1) { http_response_code(404); return; } $data = (object)mysqli_fetch_assoc($res); $resL = $scon->query('SELECT id,ordr,name FROM lists WHERE board="'.$_REQUEST['bid'].'" ORDER BY ordr asc'); $resC = $scon->query('SELECT list,id,parent,title,tags,color,cdate,mdate,description FROM cards WHERE board="'.$_REQUEST['bid'].'"'); $rowsL = mysqli_fetch_all($resL, MYSQLI_ASSOC); $rowsC = mysqli_fetch_all($resC, MYSQLI_ASSOC); $data->lists = $rowsL; $data->cards = $rowsC; header('content-disposition: attachment; filename="tellor_'.$data->id.'.json"'); echo json_encode($data); } function import() { //Import Board if($_SERVER['REQUEST_METHOD'] !== 'POST') {http_response_code(400); return;} $dataRaw = file_get_contents('php://input'); $data = json_decode($dataRaw); if(!$data) { http_response_code(400); return; } if(isset($data->nodeId)) importTrello($data); else if(isset($data->name)) importTellor($data); else http_response_code(400); } function importTellor($data) { //Import Tellor global $scon; $res = $scon->query('INSERT INTO boards(id,name,bgimg) VALUES("'.$data->id.'","'.$data->name.'","'.$data->bgimg.'")'); if(!$res) {http_response_code(500); return;} $sq = $scon->prepare('INSERT INTO lists(board,id,ordr,name) VALUES(?,?,?,?)'); foreach($data->lists as $list) { $sq->bind_param('ssssi', $data->id, $list->id, $list->ordr, $list->name); $sq->execute(); } $sq->close(); $sq = $scon->prepare('INSERT INTO cards VALUES(?,?,?,?,?,?,?,?,?,?)'); foreach($data->cards as $card) { $sq->bind_param('ssssssssss', $data->id, $card->list, $card->id, $card->parent, $card->title, $card->tags, $card->color, $card->cdate, $card->mdate, $card->description); $sq->execute(); } $sq->close(); echo $data->id; } function importTrello($data) { //Import Trello global $scon; $colorCodes = array("black" => "#000", "silver" => "#BBB", "gray" => "#888", "white" => "#FFF", "maroon" => "#900", "red" => "#F00", "purple" => "#808", "fuchsia" => "#F0F", "green" => "#080", "lime" => "#0F0", "olive" => "#880", "yellow" => "#FF0", "navy" => "#008", "blue" => "#00F", "teal" => "#088", "aqua" => "#0FF"); $bid = idGen32(); $boardName = str_replace(['\\','"','<','\n'], '', $data->name); $res = $scon->query('INSERT INTO boards(id,name,bgimg) VALUES("'.$bid.'","'.$boardName.'","'.$data->perfs->backgroundImage.'")'); if(!$res) {http_response_code(500); return;} usort($data->cards, function($a, $b) {return $a->pos - $b->pos;}); $sqlCards = $scon->prepare('INSERT INTO cards VALUES(?,?,?,?,?,?,null,default,?,?)'); $sqlLists = $scon->prepare('INSERT INTO lists(board,id,ordr,name) VALUES(?,?,?,?)'); foreach($data->lists as $list) { if($list->closed) continue; $lid = idGen32(); $sqlLists->bind_param('sssis', $bid, $lid, $list->pos, $list->name); $sqlLists->execute(); $pid = "0"; foreach($data->cards as $card) { if($card->closed || $card->idList !== $list->id) continue; $cid = idGen32(); $tags = ""; foreach($card->labels as $label) { //tags if(empty($label->color)) continue; if(str_starts_with($label->color, '#')) $tag = substr($label->color, 1); else $tag = $colorCodes[$label->color]; if($tag) $tags .= $tag . ' '; } $tags = trim($tags); $sqlCards->bind_param('ssssssss', $bid, $lid, $cid, $pid, $card->name, $tags, $card->dateLastActivity, $card->desc); $sqlCards->execute(); $pid = $cid; } } $sqlLists->close(); $sqlCards->close(); echo $bid; } function idGen32() { $chars = ''; $bytes = random_bytes(16); for($i = 0; $i < 16; $i++) { $byte = ord($bytes[$i]) & 31; $byte += ($byte > 9) ? 87 : 48; $chars .= chr($byte); } return $chars; } ?>