I’m of two minds about posting this. The code is ugly. I’m posting it anyway because there aren’t a lot of resource for this this and was kind of a bear. I hope to save somebody else some trouble.
As is well-documented, I’m a fan of productivity tools. Todoist added boards to support a kanban format and let me tell you, I am here for it.
My productivity software journey of the past couple years goes like this: Trello and Todoist -> Asana -> Trello alone -> Zenkit -> Todoist alone. I have data living in all four places. I’m trying to move as much as I can to Todoist (it has global tags which Trello lacks, but its missing some other features I’m pretty fond of). Definitely Todoist will last forever and I won’t come running back to Trello.
Don’t forget to put in your API key.
<?php
/*
* Create a Todoist CSV file from the Zenkit API. Resulting file requires some
* manual work.
*
* @license: GPL
* @author KJ Coop
*
* Zenkit is more complicated then Todoist, so we kind of have to wedge things
* in. For example, Zenkit only has groups of labels, and you decide which ones
* you want to use a section heads at any given moment. Consequently, when
* Zenkit hands off its data, it's not easy to see which labels Todoist should
* use as section heads and which it should use as labels. It's probably
* possible to detect which labels are grouped together in the Zenkit output,
* but I haven't put any effort into it (yet?).
*
* In Todoist, to add a label, you append the title with @Desired_Label. Because
* we don't know whether we want Todoist to show any given label as a label or
* a section head, it appends all the label-like strings as labels.
* Consequently, if you want to keep your kanban format and of course you do or
* what is even the point of being alive, you have to open up the finished
* CSV file an manually manipulate it.
*
* @todo Things I haven't put any work into:
* - Descriptions - Zenkit has them whereas Todoist does not
* - Attachments - in Zenkit, they can be on a file. In Todoist, they're
* necessarily in a comment
* - Comments
* - Users
*
*
*
* Links:
* - https://todoist.com/help/articles/how-to-format-your-csv-file-so-you-can-import-it-into-todoist
* - https://base.zenkit.com/docs/api/overview/introduction
* - https://packagist.org/packages/idoit/zenkit-api-client
*/
require_once(__DIR__ . "/vendor/autoload.php");
use idoit\zenkit\API;
function appendToCsv($filename, $type, $content) {
// https://todoist.com/help/articles/how-to-format-your-csv-file-so-you-can-import-it-into-todoist
$array = [
$type,
$content,
'4', // Priority
'1', // Indent
'', // Author
'', // Responsible
'', // Date
'', // Date lang
'', // Timezone
];
$csv_file = __DIR__.'/'.$filename.'.csv';
// https://www.geeksforgeeks.org/how-to-convert-an-array-to-csv-file-in-php/
$fp = fopen($csv_file, 'a');
// Loop through file pointer and a line
fputcsv($fp, $array);
fclose($fp);
}
function listWorkspacesAndBoards() {
$apiKey = '';
$api = new API($apiKey);
$count = 0;
// Some of the arrays Zenkit returns are like this and we can ignore them.
$emptyArray = [
'text' => '',
'searchText' => '',
'textType' => 'plain'
];
// Output data as array - workspaces and their collections/lists.
$workspaces = $api->getWorkspaceService()->getAllWorkspacesAndLists();
foreach ($workspaces as $key => $workspace) {
$filename = $workspace->id;
foreach ($workspace->lists as $list_key => $list_item) {
$elements = $api->getElementService()
->getElementsInList($list_item->shortId);
$entries = $api->getEntryService()
->setElementConfiguration($elements)
->getEntriesForListView($list_item->shortId);
// ->getEntry($list_item->shortId, 1);
$listEntries = $entries->listEntries;
$distinctTitles = [];
$distinctWholeStrings = [];
if (!is_array($listEntries)) {
continue;
}
foreach ($listEntries as $entry) {
if (isset($entry->displayString)) {
$title = '';
foreach ($entry->elements as $element) {
/*
Don't reset the title at each iteration of this
loop. We append labels in subsequent iterations.
Fesetting the title resets the whole task. Consider:
In Zenkit, you have something like the following:
Task 1
* Label A
* Label B
Task 2
The best sense of the API I can get is that it gives
you:
Task 1
Label A
Label B
Task 2
The code can see that Task 1 is a task and that the
labels are a different kind of string that relates
to the last thing it encountered. If doesn't know
until Task 2 comes along that it's finished with
Task 1. Until it sees a task, it has to keep
appending tasks to the last title it encountered.
*/
if (isset($element['text']) && $element['text'] != '' && !strpos($element['text'], 'http')) {
// Remember that this is a title
$title = $element['text'];
$distinctTitles[md5($element['text'])] = $element['text'];
} else if (isset($element['categoriesSort']) && isset($element['categoriesSort'])) {
foreach ($element['categoriesSort'] as $cat) {
if (isset($distinctTitles[md5($title)])) {
/*
Add categories as labels. This is an not
ideal map - Zenkit sees no distinction
between labels and column heads,
wbereas Todoist does. We add them all as
labels if you want to change it to a
section head, you must alter the
resulting CSV manually :(
*/
$distinctTitles[md5($title)] .= ' @'.str_replace(' ', '_', $cat->name);
}
}
} else if ($element == $emptyArray) {
// Silence it
} else if (isset($element['persons'])) {
// Silence it - I'm the only person who matters, obviously
} else if (isset($element['files']) && isset($element['filesData'])) {
// Not worried about files right now.
} else {
// IDK LOL OMG
}
// Remember the whole string.
$distinctWholeStrings[md5($title)] = $title;
}
}
}
}
}
echo "Distinct Titles: \n";
print_r($distinctTitles);
foreach ($distinctTitles as $title) {
appendToCsv($filename, 'task', $title);
}
}
try {
listWorkspacesAndBoards();
echo "\n";
} catch (idoit\zenkit\BadResponseException $e) {
echo 'Exception! The status was ' . $e->getCode() . ', response: ' . $e->getResponse()->getBody()->getContents();
} catch (Exception $e) {
echo 'Exception! Something else went wrong: ' . $e->getMessage();
}