├── .editorconfig ├── README.md ├── action.php ├── conf ├── default.php └── metadata.php ├── lang ├── cs │ ├── lang.php │ └── settings.php ├── da │ ├── lang.php │ └── settings.php ├── de │ ├── lang.php │ └── settings.php ├── en │ ├── lang.php │ └── settings.php ├── eo │ ├── lang.php │ └── settings.php ├── fr │ ├── lang.php │ └── settings.php ├── it │ └── lang.php ├── ja │ ├── lang.php │ └── settings.php ├── ko │ ├── lang.php │ └── settings.php ├── nl │ ├── lang.php │ └── settings.php ├── pt-br │ ├── lang.php │ └── settings.php ├── ru │ ├── lang.php │ └── settings.php ├── sv │ ├── lang.php │ └── settings.php ├── tr │ ├── lang.php │ └── settings.php ├── zh-tw │ └── settings.php └── zh │ ├── lang.php │ └── settings.php ├── plugin.info.txt ├── script.js ├── style.css ├── syntax ├── list.php └── todo.php └── todo.png /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{js,php}] 4 | indent_style = space 5 | indent_size = 4 6 | charset = utf-8 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DokuWiki Plugin ToDo 2 | ===== 3 | 4 | see https://www.dokuwiki.org/plugin:todo 5 | 6 | 7 | Notes for Collaborators: 8 | ==== 9 | To prepare a new release: 10 | - Take note of latest date tag, i.e. "2023-05-17" and edit "latest" release to be based on that instead of 'latest' tag. 11 | - Move `latest` tag to current commit and add current date tag 12 | ``` 13 | # commit changes etc. 14 | git push origin 15 | git tag `date +%F` 16 | git tag --force latest 17 | git push origin `date +%F` 18 | # this should only update the tag, not any commits... 19 | git push -f origin latest 20 | ``` 21 | - Create a new release based on `latest` tag. 22 | 23 | To update local tags to match remote tags (i.e. reflect a move of the 'latest' tag) you need to 24 | ``` 25 | git fetch --tags -f 26 | ``` 27 | because the updated tag is refused otherwise. 28 | -------------------------------------------------------------------------------- /action.php: -------------------------------------------------------------------------------- 1 | 8 | * @date 20130405 Leo Eibler \n 9 | * replace old sack() method with new jQuery method and use post instead of get \n 10 | * @date 20130408 Leo Eibler \n 11 | * remove getInfo() call because it's done by plugin.info.txt (since dokuwiki 2009-12-25 Lemming) 12 | */ 13 | 14 | if(!defined('DOKU_INC')) die(); 15 | /** 16 | * Class action_plugin_todo registers actions 17 | */ 18 | class action_plugin_todo extends DokuWiki_Action_Plugin { 19 | 20 | /** 21 | * Register the eventhandlers 22 | */ 23 | public function register(Doku_Event_Handler $controller) { 24 | $controller->register_hook('TOOLBAR_DEFINE', 'AFTER', $this, 'insert_button', array()); 25 | $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, '_ajax_call', array()); 26 | } 27 | 28 | /** 29 | * Inserts the toolbar button 30 | */ 31 | public function insert_button(&$event, $param) { 32 | $event->data[] = array( 33 | 'type' => 'format', 34 | 'title' => $this->getLang('qb_todobutton'), 35 | 'icon' => '../../plugins/todo/todo.png', 36 | // key 't' is already used for going to top of page, bug #76 37 | // 'key' => 't', 38 | 'open' => '', 39 | 'close' => '', 40 | 'block' => false, 41 | ); 42 | } 43 | 44 | /** 45 | * Handles ajax requests for to do plugin 46 | * 47 | * @brief This method is called by ajax if the user clicks on the to-do checkbox or the to-do text. 48 | * It sets the to-do state to completed or reset it to open. 49 | * 50 | * POST Parameters: 51 | * index int the position of the occurrence of the input element (starting with 0 for first element/to-do) 52 | * checked int should the to-do set to completed (1) or to open (0) 53 | * path string id/path/name of the page 54 | * 55 | * @date 20140317 Leo Eibler \n 56 | * use todo content as change description \n 57 | * @date 20131008 Gerrit Uitslag \n 58 | * move ajax.php to action.php, added lock and conflict checks and improved saving 59 | * @date 20130405 Leo Eibler \n 60 | * replace old sack() method with new jQuery method and use post instead of get \n 61 | * @date 20130407 Leo Eibler \n 62 | * add user assignment for todos \n 63 | * @date 20130408 Christian Marg \n 64 | * change only the clicked to-do item instead of all items with the same text \n 65 | * origVal is not used anymore, we use the index (occurrence) of input element \n 66 | * @date 20130408 Leo Eibler \n 67 | * migrate changes made by Christian Marg to current version of plugin \n 68 | * 69 | * 70 | * @param Doku_Event $event 71 | * @param mixed $param not defined 72 | */ 73 | public function _ajax_call(&$event, $param) { 74 | global $ID, $conf, $lang; 75 | 76 | if($event->data !== 'plugin_todo') { 77 | return; 78 | } 79 | //no other ajax call handlers needed 80 | $event->stopPropagation(); 81 | $event->preventDefault(); 82 | 83 | #Variables 84 | // by einhirn determine checkbox index by using class 'todocheckbox' 85 | if(isset($_REQUEST['mode'], $_REQUEST['pageid'])) { 86 | $mode = $_REQUEST['mode']; 87 | // path = page ID 88 | $ID = cleanID(urldecode($_REQUEST['pageid'])); 89 | } else { 90 | return; 91 | } 92 | 93 | if($mode == 'checkbox') { 94 | if(isset($_REQUEST['index'], $_REQUEST['checked'], $_REQUEST['pageid'])) { 95 | // index = position of occurrence of element (starting with 0 for first element) 96 | $index = (int) $_REQUEST['index']; 97 | // checked = flag if input is checked means to do is complete (1) or not (0) 98 | $checked = (boolean) urldecode($_REQUEST['checked']); 99 | } else { 100 | return; 101 | } 102 | } 103 | 104 | $date = 0; 105 | if(isset($_REQUEST['date'])) $date = (int) $_REQUEST['date']; 106 | 107 | $INFO = pageinfo(); 108 | 109 | #Determine Permissions 110 | if(auth_quickaclcheck($ID) < AUTH_EDIT) { 111 | echo "You do not have permission to edit this file.\nAccess was denied."; 112 | return; 113 | } 114 | // Check, if page is locked 115 | if(checklock($ID)) { 116 | $locktime = filemtime(wikiLockFN($ID)); 117 | $expire = dformat($locktime + $conf['locktime']); 118 | $min = round(($conf['locktime'] - (time() - $locktime)) / 60); 119 | 120 | $msg = $this->getLang('lockedpage').' 121 | '.$lang['lockedby'] . ': ' . editorinfo($INFO['locked']) . ' 122 | ' . $lang['lockexpire'] . ': ' . $expire . ' (' . $min . ' min)'; 123 | $this->printJson(array('message' => $msg)); 124 | return; 125 | } 126 | 127 | //conflict check 128 | if($date != 0 && $INFO['meta']['date']['modified'] > $date) { 129 | $this->printJson(array('message' => $this->getLang('refreshpage'))); 130 | return; 131 | } 132 | 133 | #Retrieve Page Contents 134 | $wikitext = rawWiki($ID); 135 | 136 | switch($mode) { 137 | case 'checkbox': 138 | #Determine position of tag 139 | if($index >= 0) { 140 | $index++; 141 | // index is only set on the current page with the todos 142 | // the occurances are counted, untill the index-th input is reached which is updated 143 | $todoTagStartPos = $this->_strnpos($wikitext, '', $todoTagStartPos) + 1; 145 | 146 | if($todoTagStartPos!==false && $todoTagEndPos > $todoTagStartPos) { 147 | // @date 20140714 le add todo text to minorchange 148 | $todoTextEndPos = strpos( $wikitext, '_buildTodoTag($oldTag, $checked); 153 | $wikitext = substr_replace($wikitext, $newTag, $todoTagStartPos, ($todoTagEndPos - $todoTagStartPos)); 154 | 155 | // save Update (Minor) 156 | lock($ID); 157 | // @date 20140714 le add todo text to minorchange, use different message for checked or unchecked 158 | saveWikiText($ID, $wikitext, $this->getLang($checked?'checkboxchange_on':'checkboxchange_off').': '.$todoText, $minoredit = true); 159 | unlock($ID); 160 | 161 | $return = array( 162 | 'date' => @filemtime(wikiFN($ID)), 163 | 'succeed' => true 164 | ); 165 | $this->printJson($return); 166 | 167 | } 168 | } 169 | break; 170 | case 'uncheckall': 171 | $newWikitext = preg_replace('/(\s]*)(.*?>|\s.*?<\/todo>)/', '$1$3', $wikitext); 172 | 173 | lock($ID); 174 | saveWikiText($ID, $newWikitext, 'Unchecked all ToDos', $minoredit = true); 175 | unlock($ID); 176 | 177 | $return = array( 178 | 'date' => @filemtime(wikiFN($ID)), 179 | 'succeed' => true 180 | ); 181 | $this->printJson($return); 182 | break; 183 | } 184 | } 185 | 186 | /** 187 | * Encode and print an arbitrary variable into JSON format 188 | * 189 | * @param mixed $return 190 | */ 191 | private function printJson($return) { 192 | $json = new JSON(); 193 | echo $json->encode($return); 194 | } 195 | 196 | /** 197 | * @brief gets current to-do tag and returns a new one depending on checked 198 | * @param $todoTag string current to-do tag e.g. 199 | * @param $checked int check flag (todo completed=1, todo uncompleted=0) 200 | * @return string new to-do completed or uncompleted tag e.g. 201 | */ 202 | private function _buildTodoTag($todoTag, $checked) { 203 | $user = ''; 204 | if($checked == 1) { 205 | if(!empty($_SERVER['REMOTE_USER'])) { $user = $_SERVER['REMOTE_USER']; } 206 | $newTag = preg_replace('/>/', ' #'.$user.':'.date('Y-m-d').'>', $todoTag); 207 | } else { 208 | $newTag = preg_replace('/[\s]*[#].*>/', '>', $todoTag); 209 | } 210 | return $newTag; 211 | } 212 | 213 | /** 214 | * Find position of $occurance-th $needle in haystack 215 | */ 216 | private function _strnpos($haystack, $needle, $occurance, $pos = 0) { 217 | for($i = 1; $i <= $occurance; $i++) { 218 | $pos = strpos($haystack, $needle, $pos); 219 | 220 | if ($pos===false) {return false; } 221 | 222 | $pos++; 223 | } 224 | return $pos - 1; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /conf/default.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | $meta['AllowLinks'] = array('onoff'); 10 | $meta['ActionNamespace'] = array('string'); 11 | $meta['Strikethrough'] = array('onoff'); 12 | $meta['CheckboxText'] = array('onoff'); 13 | $meta['Checkbox'] = array('onoff'); 14 | $meta['Header'] = array('multichoice', '_choices' => array('id','firstheader','none')); 15 | $meta['Username'] = array('multichoice', '_choices' => array('user','real','none')); 16 | $meta['ShowdateTag'] = array('onoff'); 17 | $meta['ShowdateList'] = array('onoff'); 18 | 19 | //Setup VIM: ex: et ts=2 enc=utf-8 : 20 | -------------------------------------------------------------------------------- /lang/cs/lang.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['qb_todobutton'] = 'Označit text jako Úkol'; 9 | $lang['refreshpage'] = 'Je dostupná novější verze této stránky, před pokračováním ji obnovte.'; 10 | $lang['checkboxchange_on'] = 'Úkol zaškrtnut'; 11 | $lang['checkboxchange_off'] = 'Úkol nezaškrtnut'; 12 | -------------------------------------------------------------------------------- /lang/cs/settings.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['AllowLinks'] = 'Povolit akcím také odkazovat na stránky se stejným jménem?'; 9 | $lang['ActionNamespace'] = 'V jakém jmenném prostoru mají být vaše akce vytvořeny (".:" = Current NS, Blank = Root NS)'; 10 | $lang['Strikethrough'] = 'Má být text akce přešrktnut po jejím zaškrtnutí?'; 11 | $lang['CheckboxText'] = 'Pokud je AllowLinks vypnut, má být akce označena za hotovou po kliknutí na její text?'; 12 | $lang['Checkbox'] = '(Výchozí hodnota pro nastavení "checkbox") Má být CheckBox zobrazen jako seznam?'; 13 | $lang['Header'] = '(Výchozí hodnota pro nastavení "header") Jak má být pojmenována hlavička seznamu? Jako "id" nebo jako první hlavička na stránce "firstheader" nebo bez hlavičky "none".'; 14 | $lang['Username'] = '(Výchozí hodnota pro nastavení "username") Jak má být zobrazeno jméno přiděleného uživatele? Jako "username", celé jméno "real" nebo vůbec "none"'; 15 | $lang['ShowdateTag'] = '(Výchozí hodnota pro nastavení "shodate") Má být datum začátku/splnění zobrazeno v náhledu definice tagu?'; 16 | $lang['ShowdateList'] = '(Výchozí hodnota pro nastavení "showdate") Má být datum začátku/splnění vykresleno v zobrazení seznamu?'; 17 | -------------------------------------------------------------------------------- /lang/da/lang.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['qb_todobutton'] = 'Markér tekst som gøremål'; 9 | $lang['refreshpage'] = 'En nyere version af denne side er tilgængelig. Opdater din side, før du prøver igen.'; 10 | $lang['checkboxchange_on'] = 'Gøremål tjekket'; 11 | $lang['checkboxchange_off'] = 'Gøremål ikke tjekket'; 12 | -------------------------------------------------------------------------------- /lang/da/settings.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['AllowLinks'] = 'Tillad at handlinger også linker til sider med samme navn?'; 9 | $lang['ActionNamespace'] = 'I hvilket navnerum skal dine handlinger oprettes (".:" = Nuværende NR, Blankt = Rod-NR)'; 10 | $lang['Strikethrough'] = 'Skal handlingerne gennemstreges, når de er tjekket?'; 11 | $lang['CheckboxText'] = 'Skal klik på handling markere handlingen som fuldført, hvis AllowLinks er deaktiveret?'; 12 | $lang['Checkbox'] = '(Standardværdi for indstilling "checkbox") Skal CheckBox vises i en listevisning?'; 13 | $lang['Header'] = '(Standardværdi for indstilling "header") Hvordan skal headeren for en liste navngives? Som "id" eller som titel for siden "firstheader" eller ingen header "none".'; 14 | $lang['Username'] = '(Standardværdi for indstilling "username") Hvordan skal navnet for den tildelte bruger vises? Som brugernavn "username", fuldt navn "real" eller ikke "none".'; 15 | $lang['ShowdateTag'] = '(Standardværdi for indstilling "showdate") Skal start/forfaldsdato vises i tag definitionsvisningen?'; 16 | $lang['ShowdateList'] = '(Standardværdi for indstilling "showdate") Skal start/forfaldsdatoen vises i en listevisning?'; 17 | -------------------------------------------------------------------------------- /lang/de/lang.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 20130405 Leo Eibler \n 8 | * create german language file \n 9 | * @date 20140317 Leo Eibler \n 10 | * add 'refreshpage', 'checkboxchange_on' and 'checkboxchange_off' \n 11 | */ 12 | 13 | // custom language strings for the plugin 14 | $lang['qb_todobutton'] = 'Markierten Text als Aufgabe/Todo'; 15 | $lang['refreshpage'] = 'Eine neue Version dieser Seite ist verfügbar. Bitte laden Sie die Seite neu.'; 16 | $lang['checkboxchange_on'] = 'ToDo erledigt'; 17 | $lang['checkboxchange_off'] = 'ToDo unerledigt'; 18 | 19 | //Setup VIM: ex: et ts=2 enc=utf-8 : -------------------------------------------------------------------------------- /lang/de/settings.php: -------------------------------------------------------------------------------- 1 | 6 | * @date 20130405 Leo Eibler \n 7 | * create german language file \n 8 | */ 9 | 10 | $lang['AllowLinks'] = 'Sollen Aufgaben/Todos auch auf Seiten mit dem gleichen Namen verlinken?'; 11 | $lang['ActionNamespace'] = 'Wenn AllowLinks aktiv ist, mit welchem Namespace sollen die Aufgaben-Seiten erstellt werden (".:" = Aktueller NS, leer = Root NS)'; 12 | $lang['Strikethrough'] = 'Sollen erledigte Aufgaben durchgestrichen werden?'; 13 | $lang['CheckboxText'] = 'Wenn AllowLinks nicht aktiv ist, soll dann ein Klick auf die Aufgabe, die Aufgabe als erledigt markieren?'; 14 | $lang['Checkbox'] = '(Default Wert für Option "checkbox") Soll in Listen die CheckBox aufscheinen?'; 15 | $lang['Header'] = '(Default Wert für Option "header) Wie soll der Kopf bei Listen beschriftet werden? Als "id" oder mit der ersten Kopfzeile der Seite "firstheader" oder ohne Kopfzeile "none"".'; 16 | $lang['Username'] = '(Default Wert für Option "username") Wie soll der Username ausgegeben werden? Als "username", voller Name "real" oder garnicht "none"'; 17 | $lang['ShowdateTag'] = '(Default Wert für Option "showdate") Soll das Start/Fällig-Datum bei Tag-Definitionen ausgegeben werden?'; 18 | $lang['ShowdateList'] = '(Default Wert für Option "showdate") Soll das Start/Fällig-Datum bei Listen ausgegeben werden?'; 19 | 20 | //Setup VIM: ex: et ts=2 enc=utf-8 : 21 | -------------------------------------------------------------------------------- /lang/en/lang.php: -------------------------------------------------------------------------------- 1 | 7 | * @date 20140317 Leo Eibler \n 8 | * replace 'checkboxchange' with 'checkboxchange_on' and 'checkboxchange_off' \n 9 | */ 10 | 11 | // custom language strings for the plugin 12 | $lang['qb_todobutton'] = 'Mark text as ToDo'; 13 | $lang['refreshpage'] = 'A newer version of this page is available, refresh your page before trying again.'; 14 | $lang['checkboxchange_on'] = 'ToDo checked'; 15 | $lang['checkboxchange_off'] = 'ToDo unchecked'; 16 | 17 | //Setup VIM: ex: et ts=2 enc=utf-8 : -------------------------------------------------------------------------------- /lang/en/settings.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | $lang['AllowLinks'] = 'Allow actions to also link to pages with the same name?'; 9 | $lang['ActionNamespace'] = 'What namespace should your actions be created in (".:" = Current NS, Blank = Root NS)'; 10 | $lang['Strikethrough'] = 'Should the actions have strikethrough applied when checked?'; 11 | $lang['CheckboxText'] = 'If AllowLinks is disabled, should clicking the actions\' text mark the action complete?'; 12 | $lang['Checkbox'] = '(Default value for option "checkbox") Should the CheckBox be rendered in a list view?'; 13 | $lang['Header'] = '(Default value for option "header") How should the header of a list be named? As "id" or as the first header of the page "firstheader" or no header at all "none".'; 14 | $lang['Username'] = '(Default value for option "username") How should the name of the assigned user be rendered? As "username", full name "real" oder not at all "none"'; 15 | $lang['ShowdateTag'] = '(Default value for option "showdate") Should the Start/Due-date be rendered in tag definition view?'; 16 | $lang['ShowdateList'] = '(Default value for option "showdate") Should the Start/Due-date be rendered in list view?'; 17 | //Setup VIM: ex: et ts=2 enc=utf-8 : 18 | -------------------------------------------------------------------------------- /lang/eo/lang.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['qb_todobutton'] = 'Marki tekston kiel taskon'; 9 | $lang['refreshpage'] = 'Pli freŝa versio de tiu paĝo haveblas, reŝargu la paĝon antaŭ ol reprovi.'; 10 | $lang['checkboxchange_on'] = 'Plenumita tasko'; 11 | $lang['checkboxchange_off'] = 'Neplenumita tasko'; 12 | -------------------------------------------------------------------------------- /lang/eo/settings.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['AllowLinks'] = 'Ĉu taskoj rajtas ligi al samnomaj paĝoj?'; 9 | $lang['ActionNamespace'] = 'En kiu nomspaco viaj taskoj kreiĝu? (",:" = momenta NS, malplena = ĉefa NS)'; 10 | $lang['Strikethrough'] = 'Ĉu plenumitaj taskoj estu trastrekitaj?'; 11 | $lang['CheckboxText'] = 'Se AllowLinks malaktivas, ĉu klaki la tekston de la tasko marku ĝin plenumita?'; 12 | $lang['Checkbox'] = '(Defaŭlta valoro por la opcio "checkbox") Ĉu CheckBox estu montrata kiel listo?'; 13 | $lang['Header'] = '(Defaŭlta valoro por la opcio "header") Kiel nomi la listkapon? Ĉu "id", kiel unuan titolon de la paĝo "firstheader", aŭ ĉu neniu kaplinio "none"'; 14 | $lang['Username'] = '(Defaŭlta valoro por la opcio "username") Kiel montri la uzantonomon? Ĉu "username", plena nomo ("real") aŭ neniu ("none")?'; 15 | $lang['ShowdateTag'] = '(Defaŭlta valoro por la opcio "showdate") Ĉu montri la komencon/limdaton en la etiked-listigo?'; 16 | $lang['ShowdateList'] = '(Defaŭlta valoro por la opcio "showdate") Ĉu montri la komencon/limdaton en lista aspekto?'; 17 | -------------------------------------------------------------------------------- /lang/fr/lang.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['qb_todobutton'] = 'Marquer le texte comme tâche.'; 9 | $lang['refreshpage'] = 'Une nouvelle révision de cette page est disponible. Veuillez rafraîchir la page avant d\'essayer à nouveau.'; 10 | $lang['checkboxchange_on'] = 'tâche cochée'; 11 | $lang['checkboxchange_off'] = 'tâche décochée'; 12 | -------------------------------------------------------------------------------- /lang/fr/settings.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['AllowLinks'] = 'Autoriser les tâches à pointer également vers des pages de même nom.'; 9 | $lang['ActionNamespace'] = 'Dans quelle catégorie créer les tâches. (".:" = catégorie courante, vide = racine)'; 10 | $lang['Strikethrough'] = 'Les tâches devraient-elles être barrées lorsqu\'elles sont cochées ?'; 11 | $lang['CheckboxText'] = 'Si "autoriser les liens" (AllowLinks) est désactivée, est-ce que cliquer sur le texte d\'une tâche devrait marquer la tâche comme réalisée ?'; 12 | $lang['Checkbox'] = 'Valeur par défaut de l\'option "Case à cocher" ("checkbox"). Faut-il montrer les cases à cocher dans les vues en liste ? '; 13 | $lang['Header'] = 'Valeur par défaut de l\'option "Entête" ("header"). Comment nommer l\'entête d\'une liste ? par son identifiant ("id"), comme le premier entête de la page "firstheader", ou pas d\'entête du tout "none" ?'; 14 | $lang['Username'] = 'Valeur par défaut de l\'option "nom d\'utilisateur" ("username"). Comment afficher le nom d\'utilisateur ? Identifiant "username", nom complet "real" ou rien du tout "none" ?'; 15 | $lang['ShowdateTag'] = 'Valeur par défaut de l\'option "Affichage des dates" ("showdate"). Faut-il afficher les dates de début et de réalisation attendue dans les vues des définitions ?'; 16 | $lang['ShowdateList'] = 'Valeur par défaut de l\'option "Affichage des dates". Faut-il afficher les dates de début et de réalisation attendue dans les vues en liste ?'; 17 | -------------------------------------------------------------------------------- /lang/it/lang.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['qb_todobutton'] = 'Marca come ToDo'; 9 | $lang['refreshpage'] = 'È disponibile una versione più recente di questa pagina, prima di procedere è necessario ricaricare la pagina.'; 10 | $lang['checkboxchange_on'] = 'Marcato come ToDo'; 11 | $lang['checkboxchange_off'] = 'Rimossa la spunta ToDo'; 12 | -------------------------------------------------------------------------------- /lang/ja/lang.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['qb_todobutton'] = 'ToDo'; 9 | $lang['refreshpage'] = 'このページには新バージョンがあります。再試行前に再読込をしてください。'; 10 | $lang['checkboxchange_on'] = 'タスクを完了に'; 11 | $lang['checkboxchange_off'] = 'タスクを未完に'; 12 | -------------------------------------------------------------------------------- /lang/ja/settings.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['AllowLinks'] = 'タスクを同じ名前のページにリンクすることを許可しますか?'; 9 | $lang['ActionNamespace'] = 'タスクを作成する名前空間(".:" = 現在の名前空間、空白 = ルート名前空間)'; 10 | $lang['Strikethrough'] = 'チェックした時、タスクに取り消し線を引きますか?'; 11 | $lang['CheckboxText'] = 'AllowLinks が無効な場合、文字をクリックするとタスクを完了にしますか?'; 12 | $lang['Checkbox'] = '("checkbox" オプションのデフォルト値)リスト表示の場合、CheckBox を表示しますか?'; 13 | $lang['Header'] = '("header" オプションのデフォルト値)リストの見出しの命名方法?"id":ページID、"firstheader":ページ内の最初の見出し、"none":なし。'; 14 | $lang['Username'] = '("username" オプションのデフォルト値)任命されたユーザーの表示方法?"username":ユーザー名、"real":氏名、"none":非表示。'; 15 | $lang['ShowdateTag'] = '("showdate" オプションのデフォルト値)タグ定義表示の場合、開始日・締切日を表示しますか?'; 16 | $lang['ShowdateList'] = '("showdate" オプションのデフォルト値)リスト表示の場合、開始日・締切日を表示しますか?'; 17 | -------------------------------------------------------------------------------- /lang/ko/lang.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['qb_todobutton'] = '텍스트를 할 일로 표시'; 9 | $lang['refreshpage'] = '이 문서의 최신 판을 사용할 수 있습니다, 다시 시도하기 전에 문서를 새로 고치세요.'; 10 | $lang['checkboxchange_on'] = '할 일 선택됨'; 11 | $lang['checkboxchange_off'] = '할 일 선택되지 않음'; 12 | -------------------------------------------------------------------------------- /lang/ko/settings.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['AllowLinks'] = '같은 이름으로 된 문서에도 링크하도록 동작할까요?'; 9 | $lang['ActionNamespace'] = '행동이 만들어져야 하는 이름공간 (".:" = 현재 이름공간, 비움 = 루트 이름공간)'; 10 | $lang['Strikethrough'] = '선택되었을 때 행동에 취소선을 적용해야 합니까?'; 11 | $lang['CheckboxText'] = 'AllowLinks가 비활성화되어 있으면, 행동을 완료하기 위해 행동의 텍스트 표시를 클릭해야 합니까?'; 12 | $lang['Checkbox'] = '("checkbox" 옵션에 대한 기본값) CheckBox가 목록 보기에 렌더되어야 합니까?'; 13 | $lang['Header'] = '("header" 옵션에 대한 기본값) 어떻게 목록의 머리글이 이르러야 합니까? "id"나 문서의 첫 머리글로 하려면 "firstheader"나 모든 곳에 머리글이 없으려면 "none"입니다.'; 14 | $lang['Username'] = '("username" 옵션에 대한 기본값) 어떻게 할당된 사용자의 이름이 렌더되어야 합니까? "username"으로나, 실명으로 하려면 "real"이나 모든 곳에 없으려면 "none"입니다.'; 15 | $lang['ShowdateTag'] = '("showdate" 옵션에 대한 기본값) 시작 날짜/기한이 태그 정의 보기에 렌더되어야 합니까?'; 16 | $lang['ShowdateList'] = '("showdate" 옵션에 대한 기본값) 시작 날짜/기한이 목록 보기에 렌더되어야 합니까?'; 17 | -------------------------------------------------------------------------------- /lang/nl/lang.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['qb_todobutton'] = 'Markeer tekst als ToDo'; 9 | $lang['refreshpage'] = 'Een nieuwere versie van deze pagina is beschikbaar. Ververs de pagina alvorens opnieuw te proberen.'; 10 | $lang['checkboxchange_on'] = 'ToDo geselecteerd'; 11 | $lang['checkboxchange_off'] = 'ToDo niet geselecteerd'; 12 | -------------------------------------------------------------------------------- /lang/nl/settings.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['AllowLinks'] = 'Acties toestaan om ook te linken naar pagina\'s met dezelfde naam?'; 9 | $lang['ActionNamespace'] = 'In welke namespace moeten je acties worden aangemaakt? (".:" = huidige ns, leeg = root ns)'; 10 | $lang['Strikethrough'] = 'Moeten de acties worden doorgehaald als ze worden afgevinkt?'; 11 | $lang['CheckboxText'] = 'Als LinksToestaan is uitgeschakeld, moet een klik op de actie deze dan als gedaan markeren?'; 12 | $lang['Checkbox'] = '(Standaardwaarde voor de optie "checkbox") Moet de checkbox getoond worden in een lijstweergave?'; 13 | $lang['Header'] = '(Standaardwaarde voor de optie "header") Hoe moet de kop van een lijst worden aangeduid? Als "id", of als de eerste kop van de pagina ("firstheader"), of helemaal niet ("none").'; 14 | $lang['Username'] = '(Standaardwaarde voor de optie "username") Hoe moet de naam van de toegewezen gebruiker worden getoond? Als "username", volledige naam ("real"), of helemaal niet ("none").'; 15 | $lang['ShowdateTag'] = '(Standaardwaarde voor de optie "showdate") Moet de Start/Due-date worden getoond in tag-definitieweergave?'; 16 | $lang['ShowdateList'] = '(Standaardwaarde voor de optie "showdate") Moet de Start/Due-date worden getoond in lijstweergave?'; 17 | -------------------------------------------------------------------------------- /lang/pt-br/lang.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | // custom language strings for the plugin 10 | $lang['qb_todobutton'] = 'Marcar texto como tarefa'; 11 | $lang['refreshpage'] = 'Uma nova versão desta página está disponível. Atualize a página antes de tentar novamente.'; 12 | $lang['checkboxchange_on'] = 'Tarefa marcada'; 13 | $lang['checkboxchange_off'] = 'Tarefa desmarcada'; 14 | 15 | //Setup VIM: ex: et ts=2 enc=utf-8 : -------------------------------------------------------------------------------- /lang/pt-br/settings.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | $lang['AllowLinks'] = 'Permitir ações a linkar para páginas com o mesmo nome?'; 10 | $lang['ActionNamespace'] = 'Em que namespace suas ações devem ser criadas? (".:" = Namespace atual, Vazio = Namespace raiz)'; 11 | $lang['Strikethrough'] = 'Exibir ações marcadas como finalizadas riscadas?'; 12 | $lang['CheckboxText'] = 'Se a opção AllowLinks estiver desabilitada, clicar no texto da tarefa deve marcar a tarefa como completa?'; 13 | $lang['Checkbox'] = '(Valor padrão para a opção "checkbox") A checkbox deve ser exibida em listas?'; 14 | $lang['Header'] = '(Valor padrão para a opção "header") Qual deve ser o nome do cabeçalho de uma lista? Como "id", o primeiro cabeçalho da página ("firstheader") ou nenhum ("none")?'; 15 | $lang['Username'] = '(Valor padrão para a opção "username") Como o nome dos usuários associados às tarefas devem ser exibidos: seu nome de usuário ("username"), nome completo ("real"), ou não devem ser exibidos ("none")?'; 16 | $lang['ShowdateTag'] = '(Valor padrão para a opção "showdate") As datas de início (start-date) e conclusão (due-date) devem ser exibidas nas definições de tarefas?'; 17 | $lang['ShowdateList'] = '(Valor padrão para a opção "showdate") As datas de início (start-date) e conclusão (due-date) devem ser exibidas em listas de tarefas?'; 18 | //Setup VIM: ex: et ts=2 enc=utf-8 : 19 | -------------------------------------------------------------------------------- /lang/ru/lang.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['qb_todobutton'] = 'Отметить текст как Задача (ToDo)'; 9 | $lang['refreshpage'] = 'Доступна более новая версия этой страницы. Обновите страницу перед новой попыткой.'; 10 | $lang['checkboxchange_on'] = 'Проверенные задачи'; 11 | $lang['checkboxchange_off'] = 'Непроверенные задачи'; 12 | -------------------------------------------------------------------------------- /lang/ru/settings.php: -------------------------------------------------------------------------------- 1 | 7 | * @author k04an 8 | */ 9 | $lang['AllowLinks'] = 'Позволять Задачам (ToDo) также ссылаться на страницы с таким же названиям?'; 10 | $lang['ActionNamespace'] = 'В каком пространстве имен должны создаваться Задачи (".:" = текущее пространство имен, пустое значение - коренное пространство имен)'; 11 | $lang['Strikethrough'] = 'Должны ли Задачи становиться зачеркнутыми при их выполнении?'; 12 | $lang['CheckboxText'] = 'Если опция AllowLinks выключена, должно ли нажатие на текст Задачи помечать его как выполненное?'; 13 | $lang['Checkbox'] = '(Значение по умолчанию для "checkbox") Следует ли отображать флажок в виде списка?'; 14 | $lang['Header'] = '(Значение по умолчанию для "header") Как должен называться заголовок списка? Как "id", или как первый заголовок страницы "firstheader", или вообще без заголовка "none".'; 15 | $lang['Username'] = '(Значение по умолчанию для "username") Как должно отображаться имя назначенного пользователя? Как "username", полное имя "real" или вообще не отображаться "none".'; 16 | $lang['ShowdateTag'] = '(Значение по умолчанию для "showdate") Должна ли дата начала/окончания отображаться в виде определения тега?'; 17 | $lang['ShowdateList'] = '(Значение по умолчанию для опции "showdate") Следует ли отображать дату начала/окончания в виде списка?'; 18 | -------------------------------------------------------------------------------- /lang/sv/lang.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['qb_todobutton'] = 'Markera text som Att göra'; 9 | $lang['refreshpage'] = 'En nyare version av denna sida är tillgänglig, uppdaterad din sidan innan du försöker igen'; 10 | $lang['checkboxchange_on'] = 'Att göra-markerad'; 11 | $lang['checkboxchange_off'] = 'Att göra-avmarkerad'; 12 | -------------------------------------------------------------------------------- /lang/sv/settings.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['AllowLinks'] = 'Tillåt händelser att också länka till samma sida?'; 9 | $lang['ActionNamespace'] = 'Vilken namnrymd ska dina händelser skapas i (".:" = Nuvarande namnrymd, Blank = Grundnamnrymd)'; 10 | $lang['Strikethrough'] = 'Ska händelserna bli genomstrukna när de är markerade?'; 11 | $lang['CheckboxText'] = 'Om AllowLinks är avaktiverad ska händelsens text markeras som åtgärdad/avklarad vid klick?'; 12 | $lang['Checkbox'] = '(Standardvärde för "checkbox") Ska kryssrutan renderas i listvisning?'; 13 | $lang['Header'] = '(Standardvärde för "header") Hur ska rubriken på en lista namnges? Som "id", som den första rubriken på sidan "firstheader" eller inte alls "none".'; 14 | $lang['Username'] = '(Standardvärde för "username") Hur ska namnet på den valda användaren visas? Som "username", fullständigt namn "real" eller inte alls "none"'; 15 | $lang['ShowdateTag'] = '(Standardvärde för "showdate") Ska start-/förfallodatum renderas i taggdefinitionsvisning?'; 16 | $lang['ShowdateList'] = '(Standardvärde för "showdate") Ska start-/förfallodatum renderas i listvisning?'; 17 | -------------------------------------------------------------------------------- /lang/tr/lang.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['qb_todobutton'] = 'Metni görev olarak işaretle'; 9 | $lang['refreshpage'] = 'Bu sayfanın daha güncel bir hali bulunmaktadır, tekrar denemeden önce sayfayı tazeleyin.'; 10 | $lang['checkboxchange_on'] = 'Görev işaretlendi'; 11 | $lang['checkboxchange_off'] = 'Görevin işareti kaldırıldı'; 12 | -------------------------------------------------------------------------------- /lang/tr/settings.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['AllowLinks'] = 'Görevler aynı isme sahip sayfaya bağlantı versin mi?'; 9 | $lang['ActionNamespace'] = 'Görevlerin oluşturulacağı isim alanı (".:" = Bulunduğu isim alanı, Boş = Kök dizini)'; 10 | $lang['Strikethrough'] = 'Tamamlandı olarak işaretlenen görev metni, üzeri çizilmiş olarak gösterilsin mi?'; 11 | $lang['CheckboxText'] = 'Aynı isimli sayfaya bağlantı verme kapalıysa görev metnine tıklamak görevi tamamlandı olarak işaretlesin mi?'; 12 | -------------------------------------------------------------------------------- /lang/zh-tw/settings.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | $lang['AllowLinks'] = 'AllowLinks: ToDo是否與同名的page建立超連結?'; 9 | $lang['ActionNamespace'] = 'ToDo應該建立在哪個NS(namespace)下? (".:" =目前的NS, 空白 = Root NS)'; 10 | $lang['Strikethrough'] = '當ToDo完成後,是否套用刪除線?'; 11 | $lang['CheckboxText'] = '如果AllowLinks關閉,點擊ToDo的文字是否代表該事件己完成?'; 12 | 13 | -------------------------------------------------------------------------------- /lang/zh/lang.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['qb_todobutton'] = '标记为待办事项'; 9 | $lang['refreshpage'] = '本页面有新版本,请尝试刷新页面。'; 10 | $lang['checkboxchange_on'] = '选择待办'; 11 | $lang['checkboxchange_off'] = '取消选择待办'; 12 | -------------------------------------------------------------------------------- /lang/zh/settings.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $lang['AllowLinks'] = '是否用同名链接到动作'; 9 | $lang['ActionNamespace'] = '动作的名字 (".:" = Current NS, Blank = Root NS)'; 10 | $lang['Strikethrough'] = '当动作选中时是否打上删除线'; 11 | $lang['CheckboxText'] = '如果不允许链接,点击动作时讲此项标记完成?'; 12 | $lang['Checkbox'] = '(“复选框”的默认值)复选框是否在列表中呈现?'; 13 | $lang['Header'] = '列表标题的名称'; 14 | $lang['Username'] = '用户名的名称'; 15 | $lang['ShowdateTag'] = '是否在标签视图显示日期'; 16 | $lang['ShowdateList'] = '是否在列表视图显示日期'; 17 | -------------------------------------------------------------------------------- /plugin.info.txt: -------------------------------------------------------------------------------- 1 | base todo 2 | author Leo Eibler, Christian Marg, Markus Gschwendt, Robert Weinmeister 3 | email dokuwiki@sprossenwanne.at, marg@rz.tu-clausthal.de, develop@weinmeister.org 4 | date 2024-05-10 5 | name ToDo 6 | desc Create a checkbox based todo list with optional user assignment (basic syntax: This is a ToDo). It can also be used as a lightweight task list management system. 7 | url https://www.dokuwiki.org/plugin:todo 8 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @date 20130405 Leo Eibler \n 3 | * replace old sack() method with new jQuery method and use post instead of get - see https://www.dokuwiki.org/devel:jqueryfaq \n 4 | * @date 20130407 Leo Eibler \n 5 | * use jQuery for finding the elements \n 6 | * @date 20130408 Christian Marg \n 7 | * change only the clicked todoitem instead of all items with the same text \n 8 | * @date 20130408 Leo Eibler \n 9 | * migrate changes made by Christian Marg to current version of plugin (use jQuery) \n 10 | * @date 20130410 by Leo Eibler / http://www.eibler.at \n 11 | * bugfix: encoding html code (security risk ) - bug reported by Andreas \n 12 | * @date 20130413 Christian Marg \n 13 | * bugfix: chk.attr('checked') returns checkbox state from html - use chk.is(':checked') - see http://www.unforastero.de/jquery/checkbox-angehakt.php \n 14 | * @date 20130413 by Leo Eibler / http://www.eibler.at \n 15 | * bugfix: config option Strikethrough \n 16 | */ 17 | 18 | /** 19 | * html-layout: 20 | * 21 | * +input[checkbox].todocheckbox 22 | * +span.todotext 23 | * -del 24 | * --span.todoinnertext 25 | * ---anchor with text or text only 26 | */ 27 | 28 | var ToDoPlugin = { 29 | /** 30 | * lock to prevent simultanous requests 31 | */ 32 | locked: false, 33 | 34 | /** 35 | * @brief onclick method for input element 36 | * 37 | * @param {jQuery} $chk the jQuery input element 38 | */ 39 | todo: function ($chk) { 40 | //skip when locked 41 | if (ToDoPlugin.locked) { 42 | return; 43 | } 44 | //set lock 45 | ToDoPlugin.locked = true; 46 | 47 | var $spanTodoinnertext = $chk.nextAll("span.todotext:first").find("span.todoinnertext"), 48 | param = $chk.data(), // contains: index, pageid, date, strikethrough 49 | checked = !$chk.is(':checked'); 50 | 51 | // if the data-index attribute is set, this is a call from the page where the todos are defined 52 | if (param.index === undefined) param.index = -1; 53 | 54 | if ($spanTodoinnertext.length) { 55 | /** 56 | * Callback function update the todoitem when save request succeed 57 | * 58 | * @param {Array} data returned by ajax request 59 | */ 60 | var whenCompleted = function (data) { 61 | //update date after edit and show alert when needed 62 | if (data.date) { 63 | jQuery('input.todocheckbox').data('date', data.date); 64 | } 65 | if (data.message) { 66 | alert(data.message); 67 | } 68 | //apply styling, or undo checking checkbox 69 | if (data.succeed) { 70 | $chk.prop('checked', checked); 71 | 72 | if (checked) { 73 | if (param.strikethrough && !$spanTodoinnertext.parent().is("del")) { 74 | $spanTodoinnertext.wrap(""); 75 | } 76 | } else { 77 | if ($spanTodoinnertext.parent().is("del")) { 78 | $spanTodoinnertext.unwrap(); 79 | } 80 | } 81 | } 82 | 83 | //release lock 84 | ToDoPlugin.locked = false; 85 | }; 86 | 87 | jQuery.post( 88 | DOKU_BASE + 'lib/exe/ajax.php', 89 | { 90 | call: 'plugin_todo', 91 | mode: 'checkbox', 92 | index: param.index, 93 | pageid: param.pageid, 94 | checked: checked ? "1" : "0", 95 | date: param.date 96 | }, 97 | whenCompleted, 98 | 'json' 99 | ); 100 | } else { 101 | alert("Appropriate javascript element not found.\nReverting checkmark."); 102 | } 103 | }, 104 | 105 | uncheckall: function () { 106 | if (ToDoPlugin.locked) { 107 | return; 108 | } 109 | ToDoPlugin.locked = true; 110 | 111 | var whenCompleted = function () { 112 | jQuery('input.todocheckbox').each(function() { 113 | jQuery(this).prop('checked', false); 114 | }); 115 | 116 | jQuery('span.todoinnertext').each(function () { 117 | if (jQuery(this).parent().is("del")) { 118 | jQuery(this).unwrap(); 119 | } 120 | }); 121 | 122 | jQuery('span.todouser').each(function () { 123 | jQuery(this).remove(); 124 | }); 125 | 126 | ToDoPlugin.locked = false; 127 | }; 128 | 129 | jQuery.post( 130 | DOKU_BASE + 'lib/exe/ajax.php', 131 | { 132 | call: 'plugin_todo', 133 | mode: 'uncheckall', 134 | pageid: jQuery('input.todocheckbox:first').data().pageid 135 | }, 136 | whenCompleted, 'json'); 137 | } 138 | }; 139 | 140 | jQuery(function () { 141 | // add handler to checkbox 142 | jQuery('input.todocheckbox').click(function (e) { 143 | e.preventDefault(); 144 | e.stopPropagation(); 145 | 146 | var $this = jQuery(this); 147 | // undo checking the checkbox 148 | $this.prop('checked', !$this.is(':checked')); 149 | 150 | ToDoPlugin.todo($this); 151 | }); 152 | 153 | // add click handler to todotext spans when marked with 'clickabletodo' 154 | jQuery('span.todotext.clickabletodo').click(function () { 155 | //Find the checkbox node we need 156 | var $chk = jQuery(this).prevAll('input.todocheckbox:first'); 157 | 158 | ToDoPlugin.todo($chk); 159 | }); 160 | 161 | // add click handler to button to uncheck all todos on its page 162 | jQuery('button.todouncheckall').click(function () { 163 | ToDoPlugin.uncheckall(); 164 | }); 165 | }); -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* 2 | ** @date 20130407 Leo Eibler \n 3 | ** add todouser for user assignment css class \n 4 | */ 5 | span.todohlght:hover{background:#DDD; cursor:default; } 6 | span.todouser { font-size: x-small; cursor:default; font-style: italic; padding-left: 3px; padding-right: 3px; margin-right: 4px; } 7 | span.tododates { font-size: x-small; padding-left: 3px; padding-right: 3px; margin-right: 4px; } 8 | span.todostarted { background:#EEA; font-weight: bold; } 9 | span.tododue { background:#EAA; font-weight: bold; } 10 | 11 | span.todolow { background-color: #ffc; } 12 | span.todomedium { background-color: #fd3; font-weight: bold; } 13 | span.todohigh { background-color: #ffbcaf; font-weight: bold; font-size:1.1em; } 14 | -------------------------------------------------------------------------------- /syntax/list.php: -------------------------------------------------------------------------------- 1 | Lexer->addSpecialPattern('~~TODOLIST[^~]*~~', $mode, 'plugin_todo_list'); 44 | } 45 | 46 | /** 47 | * Handle matches of the todolist syntax 48 | * 49 | * @param string $match The match of the syntax 50 | * @param int $state The state of the handler 51 | * @param int $pos The position in the document 52 | * @param Doku_Handler $handler The handler 53 | * @return array Data for the renderer 54 | */ 55 | public function handle($match, $state, $pos, Doku_Handler $handler) { 56 | 57 | $options = substr($match, 10, -2); // strip markup 58 | $options = explode(' ', $options); 59 | $data = array( 60 | 'header' => $this->getConf("Header"), 61 | 'completed' => 'all', 62 | 'assigned' => 'all', 63 | 'completeduserlist' => 'all', 64 | 'ns' => 'all', 65 | 'showdate' => $this->getConf("ShowdateList"), 66 | 'checkbox' => $this->getConf("Checkbox"), 67 | 'username' => $this->getConf("Username"), 68 | 'short' => false, 69 | ); 70 | $allowedvalues = array('yes', 'no'); 71 | foreach($options as $option) { 72 | @list($key, $value) = explode(':', $option, 2); 73 | switch($key) { 74 | case 'header': // how should the header be rendered? 75 | if(in_array($value, array('id', 'firstheader', 'none'))) { 76 | $data['header'] = $value; 77 | } 78 | break; 79 | case 'short': 80 | if(in_array($value, $allowedvalues)) { 81 | $data['short'] = ($value == 'yes'); 82 | } 83 | break; 84 | case 'showdate': 85 | if(in_array($value, $allowedvalues)) { 86 | $data['showdate'] = ($value == 'yes'); 87 | } 88 | break; 89 | case 'checkbox': // should checkbox be rendered? 90 | if(in_array($value, $allowedvalues)) { 91 | $data['checkbox'] = ($value == 'yes'); 92 | } 93 | break; 94 | case 'completed': 95 | if(in_array($value, $allowedvalues)) { 96 | $data['completed'] = ($value == 'yes'); 97 | } 98 | break; 99 | case 'username': // how should the username be rendered? 100 | if(in_array($value, array('user', 'real', 'none'))) { 101 | $data['username'] = $value; 102 | } 103 | break; 104 | case 'assigned': 105 | if(in_array($value, $allowedvalues)) { 106 | $data['assigned'] = ($value == 'yes'); 107 | break; 108 | } 109 | //assigned? 110 | $data['assigned'] = explode(',', $value); 111 | // @date 20140317 le: if check for logged in user, also check for logged in user email address 112 | if( in_array( '@@USER@@', $data['assigned'] ) ) { 113 | $data['assigned'][] = '@@MAIL@@'; 114 | } 115 | $data['assigned'] = array_map( array($this,"__todolistTrimUser"), $data['assigned'] ); 116 | break; 117 | case 'completeduser': 118 | $data['completeduserlist'] = explode(',', $value); 119 | // @date 20140317 le: if check for logged in user, also check for logged in user email address 120 | if(in_array('@@USER@@', $data['completeduserlist'])) { 121 | $data['completeduserlist'][] = '@@MAIL@@'; 122 | } 123 | $data['completeduserlist'] = array_map( array($this,"__todolistTrimUser"), $data['completeduserlist'] ); 124 | break; 125 | case 'ns': 126 | $data['ns'] = $value; 127 | break; 128 | case 'startbefore': 129 | list($data['startbefore'], $data['startignore']) = $this->analyseDate($value); 130 | break; 131 | case 'startafter': 132 | list($data['startafter'], $data['startignore']) = $this->analyseDate($value); 133 | break; 134 | case 'startat': 135 | list($data['startat'], $data['startignore']) = $this->analyseDate($value); 136 | break; 137 | case 'duebefore': 138 | list($data['duebefore'], $data['dueignore']) = $this->analyseDate($value); 139 | break; 140 | case 'dueafter': 141 | list($data['dueafter'], $data['dueignore']) = $this->analyseDate($value); 142 | break; 143 | case 'dueat': 144 | list($data['dueat'], $data['dueignore']) = $this->analyseDate($value); 145 | break; 146 | case 'completedbefore': 147 | list($data['completedbefore']) = $this->analyseDate($value); 148 | break; 149 | case 'completedafter': 150 | list($data['completedafter']) = $this->analyseDate($value); 151 | break; 152 | case 'completedat': 153 | list($data['completedat']) = $this->analyseDate($value); 154 | break; 155 | } 156 | } 157 | return $data; 158 | } 159 | 160 | /** 161 | * Render xhtml output or metadata 162 | * 163 | * @param string $mode Renderer mode (supported modes: xhtml) 164 | * @param Doku_Renderer $renderer The renderer 165 | * @param array $data The data from the handler() function 166 | * @return bool If rendering was successful. 167 | */ 168 | public function render($mode, Doku_Renderer $renderer, $data) { 169 | global $conf; 170 | 171 | if($mode != 'xhtml') return false; 172 | /** @var Doku_Renderer_xhtml $renderer */ 173 | 174 | $opts['pattern'] = '/]*)>(.*?)<\/todo[\W]*?>/'; //all todos in a wiki page 175 | $opts['ns'] = $data['ns']; 176 | //TODO check if storing subpatterns doesn't cost too much resources 177 | 178 | // search(&$data, $base, $func, $opts,$dir='',$lvl=1,$sort='natural') 179 | search($todopages, $conf['datadir'], array($this, 'search_todos'), $opts); //browse wiki pages with callback to search_pattern 180 | 181 | $todopages = $this->filterpages($todopages, $data); 182 | 183 | foreach($todopages as &$page) { 184 | uasort($page['todos'], function($a, $b) { 185 | if(isset($a['due']) && isset($b['due'])) { 186 | return $a['due'] <=> $b['due']; 187 | } else if (isset($a['due']) xor isset($b['due'])) { 188 | return isset($a['due']) ? -1 : 1; 189 | } else { 190 | return 0; 191 | } 192 | }); 193 | } 194 | 195 | if($data['short']) { 196 | $this->htmlShort($renderer, $todopages, $data); 197 | } else { 198 | $this->htmlTodoTable($renderer, $todopages, $data); 199 | } 200 | 201 | return true; 202 | } 203 | 204 | /** 205 | * Custom search callback 206 | * 207 | * This function is called for every found file or 208 | * directory. When a directory is given to the function it has to 209 | * decide if this directory should be traversed (true) or not (false). 210 | * Return values for files are ignored 211 | * 212 | * All functions should check the ACL for document READ rights 213 | * namespaces (directories) are NOT checked (when sneaky_index is 0) as this 214 | * would break the recursion (You can have an nonreadable dir over a readable 215 | * one deeper nested) also make sure to check the file type (for example 216 | * in case of lockfiles). 217 | * 218 | * @param array &$data - Reference to the result data structure 219 | * @param string $base - Base usually $conf['datadir'] 220 | * @param string $file - current file or directory relative to $base 221 | * @param string $type - Type either 'd' for directory or 'f' for file 222 | * @param int $lvl - Current recursion depht 223 | * @param array $opts - option array as given to search() 224 | * @return bool if this directory should be traversed (true) or not (false). Return values for files are ignored. 225 | */ 226 | public function search_todos(&$data, $base, $file, $type, $lvl, $opts) { 227 | $item['id'] = pathID($file); //get current file ID 228 | 229 | //we do nothing with directories 230 | if($type == 'd') return true; 231 | 232 | //only search txt files 233 | if(substr($file, -4) != '.txt') return true; 234 | 235 | //check ACL 236 | if(auth_quickaclcheck($item['id']) < AUTH_READ) return false; 237 | 238 | // filter namespaces 239 | if(!$this->filter_ns($item['id'], $opts['ns'])) return false; 240 | 241 | $wikitext = rawWiki($item['id']); //get wiki text 242 | 243 | // check if ~~NOTODO~~ is set on the page to skip this page 244 | if(1 == preg_match('/~~NOTODO~~/', $wikitext)) return false; 245 | 246 | $item['count'] = preg_match_all($opts['pattern'], $wikitext, $matches); //count how many times appears the pattern 247 | if(!empty($item['count'])) { //if it appears at least once 248 | $item['matches'] = $matches; 249 | $data[] = $item; 250 | } 251 | return true; 252 | } 253 | 254 | /** 255 | * filter namespaces 256 | * 257 | * @param $todopages array pages with all todoitems 258 | * @param $item string listing parameters 259 | * @return boolean if item id is in namespace 260 | */ 261 | private function filter_ns($item, $ns) { 262 | global $ID; 263 | // check if we should accept currant namespace+subnamespaces or only subnamespaces 264 | $wildsubns = substr($ns, -2) == '.:'; 265 | $onlysubns = !$wildsubns && (substr($ns, -1) == ':' || substr($ns, -2) == ':.'); 266 | // $onlyns = $onlysubns && substr($ns, -1) == '.'; 267 | 268 | // if first char of ns is '.'replace it with current ns 269 | if ($ns[0] == '.') { 270 | $ns = substr($ID, 0, strrpos($ID, ':')+1).ltrim($ns, '.:'); 271 | } 272 | $ns = trim($ns, '.:'); 273 | $len = strlen($ns); 274 | $parsepage = false; 275 | 276 | if ($parsepage = $ns == 'all') { 277 | // Always return the todo pages 278 | } elseif ($ns == '/') { 279 | // Only return the todo page if it's in the root namespace 280 | $parsepage = strpos($item, ':') === FALSE; 281 | } elseif ($wildsubns) { 282 | $p = strpos($item.':', ':', $len+1); 283 | $x = substr($item, $len+1, $p-$len); 284 | $parsepage = 0 === strpos($item, rtrim($ns.':'.$x, ':').':'); 285 | } elseif ($onlysubns) { 286 | $parsepage = 0 === strpos($item, $ns.':'); 287 | } elseif ($parsepage = substr($item, 0, $len) == $ns) { 288 | } 289 | return $parsepage; 290 | } 291 | 292 | /** 293 | * Expand assignee-placeholders 294 | * 295 | * @param $user String to be worked on 296 | * @return expanded string 297 | */ 298 | private function __todolistExpandAssignees($user) { 299 | global $USERINFO; 300 | if($user == '@@USER@@' && !empty($_SERVER['REMOTE_USER'])) { //$INPUT->server->str('REMOTE_USER') 301 | return $_SERVER['REMOTE_USER']; 302 | } 303 | // @date 20140317 le: check for logged in user email address 304 | if( $user == '@@MAIL@@' && isset( $USERINFO['mail'] ) ) { 305 | return $USERINFO['mail']; 306 | } 307 | return $user; 308 | } 309 | 310 | /** 311 | * Trim input if it's a user 312 | * 313 | * @param $user String to be worked on 314 | * @return trimmed string 315 | */ 316 | private function __todolistTrimUser($user) { 317 | //placeholder (inspired by replacement-patterns - see https://www.dokuwiki.org/namespace_templates#replacement_patterns) 318 | if( $user == '@@USER@@' || $user == '@@MAIL@@' ) { 319 | return $user; 320 | } 321 | //user 322 | return trim(ltrim($user, '@')); 323 | } 324 | 325 | /** 326 | * filter the pages 327 | * 328 | * @param $todopages array pages with all todoitems 329 | * @param $data array listing parameters 330 | * @return array of filtered pages 331 | */ 332 | private function filterpages($todopages, $data) { 333 | // skip function if $todopages has no values 334 | $pages = array(); 335 | if(isset($todopages) && count($todopages)>0) { 336 | foreach($todopages as $page) { 337 | $todos = array(); 338 | // contains 3 arrays: an array with complete matches and 2 arrays with subpatterns 339 | foreach($page['matches'][1] as $todoindex => $todomatch) { 340 | $todo = array_merge(array('todotitle' => trim($page['matches'][2][$todoindex]), 'todoindex' => $todoindex), $this->parseTodoArgs($todomatch), $data); 341 | 342 | if($this->isRequestedTodo($todo)) { 343 | $todos[] = $todo; 344 | } 345 | } 346 | if(isset($todos) && count($todos) > 0) { 347 | $pages[] = array('id' => $page['id'], 'todos' => $todos); 348 | } 349 | } 350 | } 351 | return $pages; 352 | } 353 | 354 | 355 | private function htmlShort($R, $todopages, $data) { 356 | if (is_null($todopages)) return; 357 | $done = 0; $todo = 0; 358 | foreach($todopages as $page) { 359 | foreach($page['todos'] as $value) { 360 | $todo++; 361 | if ($value['checked']) { 362 | $done++; 363 | } 364 | } 365 | } 366 | 367 | $R->cdata("($done/$todo)"); 368 | } 369 | 370 | /** 371 | * Create html for table with todos 372 | * 373 | * @param Doku_Renderer_xhtml $R 374 | * @param array $todopages 375 | * @param array $data array with rendering options 376 | */ 377 | private function htmlTodoTable($R, $todopages, $data) { 378 | if (is_null($todopages)) return; 379 | $R->table_open(); 380 | foreach($todopages as $page) { 381 | if ($data['header']!='none') { 382 | $R->tablerow_open(); 383 | $R->tableheader_open(); 384 | $R->internallink(':'.$page['id'], ($data['header']=='firstheader' ? p_get_first_heading($page['id']) : $page['id'])); 385 | $R->tableheader_close(); 386 | $R->tablerow_close(); 387 | } 388 | foreach($page['todos'] as $todo) { 389 | if(empty($todo['todotitle'])) 390 | { 391 | $todo['todotitle'] = ''; 392 | } 393 | $R->tablerow_open(); 394 | $R->tablecell_open(); 395 | $R->doc .= $this->createTodoItem($R, $page['id'], array_merge($todo, $data)); 396 | $R->tablecell_close(); 397 | $R->tablerow_close(); 398 | } 399 | } 400 | $R->table_close(); 401 | } 402 | 403 | /** 404 | * Check the conditions for adding a todoitem 405 | * 406 | * @param $data array the defined filters 407 | * @param $checked bool completion status of task; true: finished, false: open 408 | * @param $todouser string user username of user 409 | * @return bool if the todoitem should be listed 410 | */ 411 | private function isRequestedTodo($data) { 412 | //completion status 413 | $condition1 = $data['completed'] === 'all' //all 414 | || $data['completed'] === $data['checked']; //yes or no 415 | 416 | // resolve placeholder in assignees 417 | $requestedassignees = array(); 418 | if(isset($data['assigned']) && is_array($data['assigned'])) { 419 | $requestedassignees = array_map( array($this,"__todolistExpandAssignees"), $data['assigned'] ); 420 | } 421 | //assigned 422 | $condition2 = $data['assigned'] === 'all' //all 423 | || (is_bool($data['assigned']) && $data['assigned'] == $data['todouser']); //yes or no 424 | 425 | if (!$condition2 && isset($data['assigned']) && is_array($data['assigned']) && isset($data['todousers']) && is_array($data['todousers'])) 426 | foreach($data['todousers'] as $todouser) { 427 | if(in_array($todouser, $requestedassignees)) { $condition2 = true; break; } 428 | } 429 | 430 | //completed by 431 | if($condition2 && isset($data['completeduserlist']) && is_array($data['completeduserlist'])) 432 | $condition2 = in_array($data['completeduser'], $data['completeduserlist']); 433 | 434 | //compare start/due dates 435 | if($condition1 && $condition2) { 436 | $condition3s = true; $condition3d = true; 437 | if(isset($data['startbefore']) || isset($data['startafter']) || isset($data['startat'])) { 438 | if(isset($data['start'])) { 439 | if($data['startignore'] != '!') { 440 | if(isset($data['startbefore'])) { $condition3s = $condition3s && new DateTime($data['startbefore']) > $data['start']; } 441 | if(isset($data['startafter'])) { $condition3s = $condition3s && new DateTime($data['startafter']) < $data['start']; } 442 | if(isset($data['startat'])) { $condition3s = $condition3s && new DateTime($data['startat']) == $data['start']; } 443 | } 444 | } else { 445 | if(!$data['startignore'] == '*') { $condition3s = false; } 446 | if($data['startignore'] == '!') { $condition3s = false; } 447 | } 448 | } 449 | 450 | if(isset($data['duebefore']) || isset($data['dueafter']) || isset($data['dueat'])) { 451 | if(isset($data['due'])) { 452 | if($data['dueignore'] != '!') { 453 | if(isset($data['duebefore'])) { $condition3d = $condition3d && new DateTime($data['duebefore']) > $data['due']; } 454 | if(isset($data['dueafter'])) { $condition3d = $condition3d && new DateTime($data['dueafter']) < $data['due']; } 455 | if(isset($data['dueat'])) { $condition3d = $condition3d && new DateTime($data['dueat']) == $data['due']; } 456 | } 457 | } else { 458 | if(!$data['dueignore'] == '*') { $condition3d = false; } 459 | if($data['dueignore'] == '!') { $condition3d = false; } 460 | } 461 | } 462 | $condition3 = $condition3s && $condition3d; 463 | } 464 | 465 | // compare completed date 466 | $condition4 = true; 467 | if(isset($data['completedbefore'])) { 468 | $condition4 = $condition4 && new DateTime($data['completedbefore']) > $data['completeddate']; 469 | } 470 | if(isset($data['completedafter'])) { 471 | $condition4 = $condition4 && new DateTime($data['completedafter']) < $data['completeddate']; 472 | } 473 | if(isset($data['completedat'])) { 474 | $condition4 = $condition4 && new DateTime($data['completedat']) == $data['completeddate']; 475 | } 476 | 477 | return $condition1 AND $condition2 AND $condition3 AND $condition4; 478 | } 479 | 480 | 481 | /** 482 | * Analyse of relative/absolute Date and return an absolute date 483 | * 484 | * @param $date string absolute/relative value of the date to analyse 485 | * @return array absolute date or actual date if $date is invalid 486 | */ 487 | private function analyseDate($date) { 488 | $result = array($date, ''); 489 | if(is_string($date)) { 490 | if($date == '!') { 491 | $result = array('', '!'); 492 | } elseif ($date =='*') { 493 | $result = array('', '*'); 494 | } else { 495 | if(substr($date, -1) == '*') { 496 | $date = substr($date, 0, -1); 497 | $result = array($date, '*'); 498 | } 499 | 500 | if(date('Y-m-d', strtotime($date)) == $date) { 501 | $result[0] = $date; 502 | } elseif(preg_match('/^[\+\-]\d+$/', $date)) { // check if we have a valid relative value 503 | $newdate = date_create(date('Y-m-d')); 504 | date_modify($newdate, $date . ' day'); 505 | $result[0] = date_format($newdate, 'Y-m-d'); 506 | } else { 507 | $result[0] = date('Y-m-d'); 508 | } 509 | } 510 | } else { $result[0] = date('Y-m-d'); } 511 | 512 | return $result; 513 | } 514 | 515 | 516 | } 517 | -------------------------------------------------------------------------------- /syntax/todo.php: -------------------------------------------------------------------------------- 1 | Name of Action - 6 | * Creates a Checkbox with the "Name of Action" as 7 | * the text associated with it. The hash (#, optional) 8 | * will cause the checkbox to be checked by default. 9 | * See https://www.dokuwiki.org/plugin:todo#usage_and_examples 10 | * for possible options and examples. 11 | * 12 | * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 13 | * @author Babbage ; Leo Eibler 14 | */ 15 | 16 | if(!defined('DOKU_INC')) die(); 17 | 18 | /** 19 | * All DokuWiki plugins to extend the parser/rendering mechanism 20 | * need to inherit from this class 21 | */ 22 | class syntax_plugin_todo_todo extends DokuWiki_Syntax_Plugin { 23 | 24 | const TODO_UNCHECK_ALL = '~~TODO:UNCHECKALL~~'; 25 | 26 | /** 27 | * Get the type of syntax this plugin defines. 28 | * 29 | * @return String 30 | */ 31 | public function getType() { 32 | return 'substition'; 33 | } 34 | 35 | /** 36 | * Paragraph Type 37 | * 38 | * 'normal' - The plugin can be used inside paragraphs 39 | * 'block' - Open paragraphs need to be closed before plugin output 40 | * 'stack' - Special case. Plugin wraps other paragraphs. 41 | */ 42 | function getPType(){ 43 | return 'normal'; 44 | } 45 | 46 | /** 47 | * Where to sort in? 48 | * 49 | * @return Integer 50 | */ 51 | public function getSort() { 52 | return 999; 53 | } 54 | 55 | /** 56 | * Connect lookup pattern to lexer. 57 | * 58 | * @param $mode String The desired rendermode. 59 | * @return void 60 | * @see render() 61 | */ 62 | public function connectTo($mode) { 63 | $this->Lexer->addEntryPattern('(?=.*?)', $mode, 'plugin_todo_todo'); 64 | $this->Lexer->addSpecialPattern(self::TODO_UNCHECK_ALL, $mode, 'plugin_todo_todo'); 65 | $this->Lexer->addSpecialPattern('~~NOTODO~~', $mode, 'plugin_todo_todo'); 66 | } 67 | 68 | public function postConnect() { 69 | $this->Lexer->addExitPattern('', 'plugin_todo_todo'); 70 | } 71 | 72 | /** 73 | * Handler to prepare matched data for the rendering process. 74 | * 75 | * @param $match string The text matched by the patterns. 76 | * @param $state int The lexer state for the match. 77 | * @param $pos int The character position of the matched text. 78 | * @param $handler Doku_Handler Reference to the Doku_Handler object. 79 | * @return array An empty array for most cases, except: 80 | - DOKU_LEXER_EXIT: An array containing the current lexer state 81 | and information about the just lexed todo. 82 | - DOKU_LEXER_SPECIAL: For the special pattern of the Uncheck-All-Button, an 83 | array containing the current lexer state and the matched text. 84 | */ 85 | public function handle($match, $state, $pos, Doku_Handler $handler) { 86 | switch($state) { 87 | case DOKU_LEXER_ENTER : 88 | #Search to see if the '#' is in the todotag (if so, this means the Action has been completed) 89 | $x = preg_match('%]*)>%i', $match, $tododata); 90 | if($x) { 91 | $handler->todoargs = $this->parseTodoArgs($tododata[1]); 92 | } 93 | if(!isset($handler->todo_index) || !is_numeric($handler->todo_index)) { 94 | $handler->todo_index = 0; 95 | } 96 | $handler->todo_user = ''; 97 | $handler->checked = ''; 98 | $handler->todotitle = ''; 99 | break; 100 | case DOKU_LEXER_MATCHED : 101 | break; 102 | case DOKU_LEXER_UNMATCHED : 103 | /** 104 | * Structure: 105 | * input(checkbox) 106 | * 107 | * - (if links is on) or (if links is off) 108 | * -- (if strikethrough is on) or --NOTHING-- 109 | * - or 110 | * 111 | */ 112 | $handler->todotitle = $match; 113 | break; 114 | case DOKU_LEXER_EXIT : 115 | $data = array_merge(array ($state, 'todotitle' => $handler->todotitle, 'todoindex' => $handler->todo_index, 'todouser' => $handler->todo_user, 'checked' => $handler->checked), $handler->todoargs); 116 | $handler->todo_index++; 117 | #Delete temporary checked variable 118 | unset($handler->todo_user); 119 | unset($handler->checked); 120 | unset($handler->todoargs); 121 | unset($handler->todotitle); 122 | return $data; 123 | case DOKU_LEXER_SPECIAL : 124 | if($match == self::TODO_UNCHECK_ALL) { 125 | return array_merge(array($state, 'match' => $match)); 126 | } 127 | break; 128 | } 129 | return array(); 130 | } 131 | 132 | /** 133 | * Handle the actual output creation. 134 | * 135 | * @param $mode String The output format to generate. 136 | * @param $renderer Doku_Renderer A reference to the renderer object. 137 | * @param $data Array The data created by the handle() method. 138 | * @return Boolean true: if rendered successfully, or false: otherwise. 139 | */ 140 | public function render($mode, Doku_Renderer $renderer, $data) { 141 | global $ID; 142 | 143 | if(empty($data)) { 144 | return false; 145 | } 146 | 147 | $state = $data[0]; 148 | 149 | if($mode == 'xhtml') { 150 | /** @var $renderer Doku_Renderer_xhtml */ 151 | switch($state) { 152 | case DOKU_LEXER_EXIT : 153 | #Output our result 154 | $renderer->doc .= $this->createTodoItem($renderer, $ID, array_merge($data, array('checkbox'=>'yes'))); 155 | return true; 156 | case DOKU_LEXER_SPECIAL : 157 | if(isset($data['match']) && $data['match'] == self::TODO_UNCHECK_ALL) { 158 | $renderer->doc .= ''; 159 | } 160 | return true; 161 | } 162 | } 163 | return false; 164 | } 165 | 166 | /** 167 | * Parse the arguments of todotag 168 | * 169 | * @param string $todoargs 170 | * @return array(bool, false|string) with checked and user 171 | */ 172 | protected function parseTodoArgs($todoargs) { 173 | $data['checked'] = false; 174 | unset($data['start']); 175 | unset($data['due']); 176 | unset($data['completeddate']); 177 | $data['showdate'] = $this->getConf("ShowdateTag"); 178 | $data['username'] = $this->getConf("Username"); 179 | $data['priority'] = 0; 180 | $options = explode(' ', $todoargs); 181 | foreach($options as $option) { 182 | $option = trim($option); 183 | if(empty($option)) continue; 184 | if($option[0] == '@') { 185 | $data['todousers'][] = substr($option, 1); //fill todousers array 186 | if(!isset($data['todouser'])) $data['todouser'] = substr($option, 1); //set the first/main todouser 187 | } 188 | elseif($option[0] == '#') { 189 | $data['checked'] = true; 190 | @list($completeduser, $completeddate) = explode(':', $option, 2); 191 | $data['completeduser'] = substr($completeduser, 1); 192 | if(date('Y-m-d', strtotime($completeddate)) == $completeddate) { 193 | $data['completeddate'] = new DateTime($completeddate); 194 | } 195 | } 196 | elseif($option[0] == '!') { 197 | $plen = strlen($option); 198 | $excl_count = substr_count($option, "!"); 199 | if (($plen == $excl_count) && ($excl_count >= 0)) { 200 | $data['priority'] = $excl_count; 201 | } 202 | } 203 | else { 204 | @list($key, $value) = explode(':', $option, 2); 205 | switch($key) { 206 | case 'username': 207 | if(in_array($value, array('user', 'real', 'none'))) { 208 | $data['username'] = $value; 209 | } 210 | else { 211 | $data['username'] = 'none'; 212 | } 213 | break; 214 | case 'start': 215 | if(date('Y-m-d', strtotime($value)) == $value) { 216 | $data['start'] = new DateTime($value); 217 | } 218 | break; 219 | case 'due': 220 | if(date('Y-m-d', strtotime($value)) == $value) { 221 | $data['due'] = new DateTime($value); 222 | } 223 | break; 224 | case 'showdate': 225 | if(in_array($value, array('yes', 'no'))) { 226 | $data['showdate'] = ($value == 'yes'); 227 | } 228 | break; 229 | } 230 | } 231 | } 232 | return $data; 233 | } 234 | 235 | /** 236 | * @param Doku_Renderer_xhtml $renderer 237 | * @param string $id of page 238 | * @param array $data data for rendering options 239 | * @return string html of an item 240 | */ 241 | protected function createTodoItem($renderer, $id, $data) { 242 | //set correct context 243 | global $ID, $INFO; 244 | $oldID = $ID; 245 | $ID = $id; 246 | $todotitle = $data['todotitle']; 247 | $todoindex = $data['todoindex']; 248 | $checked = $data['checked']; 249 | $return = ''; 250 | 251 | if($data['checkbox']) { 252 | $return .= ' '; 258 | } 259 | 260 | // Username(s) of todouser(s) 261 | if (!isset($data['todousers'])) $data['todousers']=array(); 262 | $todousers = array(); 263 | foreach($data['todousers'] as $user) { 264 | if (($user = $this->_prepUsername($user,$data['username'])) != '') { 265 | $todousers[] = $user; 266 | } 267 | } 268 | $todouser=join(', ',$todousers); 269 | 270 | if($todouser!='') { 271 | $return .= '[' . hsc($todouser) . ']'; 272 | } 273 | if(isset($data['completeduser']) && ($checkeduser=$this->_prepUsername($data['completeduser'],$data['username']))!='') { 274 | $return .= '[' . hsc('✓ '.$checkeduser); 275 | if(isset($data['completeddate'])) { $return .= ', '.$data['completeddate']->format('Y-m-d'); } 276 | $return .= ']'; 277 | } 278 | 279 | // start/due date 280 | unset($bg); 281 | $now = new DateTime("now"); 282 | if(!$checked && (isset($data['start']) || isset($data['due'])) && (!isset($data['start']) || $data['start']<$now) && (!isset($data['due']) || $now<$data['due'])) $bg='todostarted'; 283 | if(!$checked && isset($data['due']) && $now>=$data['due']) $bg='tododue'; 284 | 285 | // show start/due date 286 | if($data['showdate'] == 1 && (isset($data['start']) || isset($data['due']))) { 287 | $return .= '['; 288 | if(isset($data['start'])) { $return .= $data['start']->format('Y-m-d'); } 289 | $return .= ' → '; 290 | if(isset($data['due'])) { $return .= $data['due']->format('Y-m-d'); } 291 | $return .= ']'; 292 | } 293 | 294 | // priority 295 | $priorityclass = ''; 296 | if (isset($data['priority'])) { 297 | $priority = $data['priority']; 298 | if ($priority == 1) $priorityclass = ' todolow'; 299 | else if ($priority == 2) $priorityclass = ' todomedium'; 300 | else if ($priority >= 3) $priorityclass = ' todohigh'; 301 | } 302 | 303 | $spanclass = 'todotext' . $priorityclass; 304 | if($this->getConf("CheckboxText") && !$this->getConf("AllowLinks") && $oldID == $ID && $data['checkbox']) { 305 | $spanclass .= ' clickabletodo todohlght'; 306 | } 307 | if(isset($bg)) $spanclass .= ' '.$bg; 308 | $return .= ''; 309 | 310 | if($checked && $this->getConf("Strikethrough")) { 311 | $return .= ''; 312 | } 313 | $return .= ''; 314 | if($this->getConf("AllowLinks")) { 315 | $return .= $this->_createLink($renderer, $todotitle, $todotitle); 316 | } else { 317 | if ($oldID != $ID) { 318 | $return .= $renderer->internallink($id, $todotitle, null, true); 319 | } else { 320 | $return .= hsc($todotitle); 321 | } 322 | } 323 | $return .= ''; 324 | 325 | if($checked && $this->getConf("Strikethrough")) { 326 | $return .= ''; 327 | } 328 | 329 | $return .= ''; 330 | 331 | //restore page ID 332 | $ID = $oldID; 333 | return $return; 334 | } 335 | 336 | /** 337 | * Prepare user name string. 338 | * 339 | * @param string $username 340 | * @param string $displaytype - one of 'user', 'real', 'none' 341 | * @return string 342 | */ 343 | private function _prepUsername($username, $displaytype) { 344 | 345 | switch ($displaytype) { 346 | case "real": 347 | global $auth; 348 | $username = $auth->getUserData($username)['name']; 349 | break; 350 | case "none": 351 | $username=""; 352 | break; 353 | case "user": 354 | default: 355 | break; 356 | } 357 | 358 | return $username; 359 | } 360 | 361 | /** 362 | * Generate links from our Actions if necessary. 363 | * 364 | * @param Doku_Renderer_xhtml $renderer 365 | * @param string $pagename 366 | * @param string $name 367 | * @return string 368 | */ 369 | private function _createLink($renderer, $pagename, $name = NULL) { 370 | $id = $this->_composePageid($pagename); 371 | 372 | return $renderer->internallink($id, $name, null, true); 373 | } 374 | 375 | /** 376 | * Compose the pageid of the pages linked by a todoitem 377 | * 378 | * @param string $pagename 379 | * @return string page id 380 | */ 381 | private function _composePageid($pagename) { 382 | #Get the ActionNamespace and make sure it ends with a : (if not, add it) 383 | $actionNamespace = $this->getConf("ActionNamespace"); 384 | if(strlen($actionNamespace) == 0 || substr($actionNamespace, -1) != ':') { 385 | $actionNamespace .= ":"; 386 | } 387 | 388 | #Replace ':' in $pagename so we don't create unnecessary namespaces 389 | $pagename = str_replace(':', '-', $pagename); 390 | 391 | //resolve and build link 392 | $id = $actionNamespace . $pagename; 393 | return $id; 394 | } 395 | 396 | } 397 | 398 | //Setup VIM: ex: et ts=4 enc=utf-8 : 399 | -------------------------------------------------------------------------------- /todo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leibler/dokuwiki-plugin-todo/4b7f33f309ab35cf0f391d6602135a4a58427bad/todo.png --------------------------------------------------------------------------------