├── .gitignore
├── todo.png
├── .editorconfig
├── lang
├── zh
│ ├── lang.php
│ └── settings.php
├── ja
│ ├── lang.php
│ └── settings.php
├── ko
│ ├── lang.php
│ └── settings.php
├── zh-tw
│ └── settings.php
├── cs
│ ├── lang.php
│ └── settings.php
├── eo
│ ├── lang.php
│ └── settings.php
├── da
│ ├── lang.php
│ └── settings.php
├── ru
│ ├── lang.php
│ └── settings.php
├── sv
│ ├── lang.php
│ └── settings.php
├── tr
│ ├── lang.php
│ └── settings.php
├── nl
│ ├── lang.php
│ └── settings.php
├── it
│ └── lang.php
├── ro
│ ├── lang.php
│ └── settings.php
├── fr
│ ├── lang.php
│ └── settings.php
├── pt-br
│ ├── lang.php
│ └── settings.php
├── en
│ ├── lang.php
│ └── settings.php
└── de
│ ├── lang.php
│ └── settings.php
├── plugin.info.txt
├── conf
├── metadata.php
└── default.php
├── style.css
├── README.md
├── script.js
├── action.php
└── syntax
├── todo.php
└── list.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/todo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leibler/dokuwiki-plugin-todo/HEAD/todo.png
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.{js,php}]
4 | indent_style = space
5 | indent_size = 4
6 | charset = utf-8
--------------------------------------------------------------------------------
/lang/zh/lang.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | $lang['qb_todobutton'] = '标记为待办事项';
9 | $lang['refreshpage'] = '本页面有新版本,请尝试刷新页面。';
10 | $lang['checkboxchange_on'] = '选择待办';
11 | $lang['checkboxchange_off'] = '取消选择待办';
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/ko/lang.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | $lang['qb_todobutton'] = '텍스트를 할 일로 표시';
9 | $lang['refreshpage'] = '이 문서의 최신 판을 사용할 수 있습니다, 다시 시도하기 전에 문서를 새로 고치세요.';
10 | $lang['checkboxchange_on'] = '할 일 선택됨';
11 | $lang['checkboxchange_off'] = '할 일 선택되지 않음';
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 |
--------------------------------------------------------------------------------
/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 2025-08-26
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 |
--------------------------------------------------------------------------------
/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/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/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/ru/lang.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | $lang['qb_todobutton'] = 'Отметить текст как Задача (ToDo)';
9 | $lang['refreshpage'] = 'Доступна более новая версия этой страницы. Обновите страницу перед новой попыткой.';
10 | $lang['checkboxchange_on'] = 'Проверенные задачи';
11 | $lang['checkboxchange_off'] = 'Непроверенные задачи';
12 |
--------------------------------------------------------------------------------
/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/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/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/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/ro/lang.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | $lang['qb_todobutton'] = 'Marchează textul ca ToDo';
9 | $lang['refreshpage'] = 'O nouă versiune a paginii este disponibilă, reîncarcă pagina înainte de a încerca din nou.';
10 | $lang['checkboxchange_on'] = 'Todo bifat';
11 | $lang['checkboxchange_off'] = 'ToDo nebifat';
12 | $lang['uncheckall_todos'] = 'Debifează toate ToDo';
13 |
--------------------------------------------------------------------------------
/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['uncheckall_todos'] = 'Décocher toutes les tâches';
13 |
--------------------------------------------------------------------------------
/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 | $lang['uncheckall_todos'] = 'Desmarcar todas as tarefas';
15 |
16 | //Setup VIM: ex: et ts=2 enc=utf-8 :
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/conf/metadata.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/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 | $lang['uncheckall_todos'] = 'Uncheck all ToDos';
17 |
18 | //Setup VIM: ex: et ts=2 enc=utf-8 :
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lang/de/lang.php:
--------------------------------------------------------------------------------
1 | \n
8 | * create german language file \n
9 | * @date 20140317 Leo Eibler \n
10 | * add 'refreshpage', 'checkboxchange_on' and 'checkboxchange_off' \n
11 | *
12 | * @author Michael Jahn
13 | * @author Leo Eibler
14 | */
15 | $lang['qb_todobutton'] = 'Markierten Text als Aufgabe/Todo';
16 | $lang['refreshpage'] = 'Eine neue Version dieser Seite ist verfügbar. Bitte laden Sie die Seite neu.';
17 | $lang['checkboxchange_on'] = 'ToDo erledigt';
18 | $lang['checkboxchange_off'] = 'ToDo unerledigt';
19 | $lang['uncheckall_todos'] = 'Alle Aufgaben abhaken';
20 |
--------------------------------------------------------------------------------
/conf/default.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/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/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/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/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/ro/settings.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | $lang['AllowLinks'] = 'Permiți acțiunilor să facă legături către paginile cu același nume?';
9 | $lang['ActionNamespace'] = 'În ce nume de spațiu vor fi create acțiunile (".:" = NS curent, Gol = NS rădăcină)';
10 | $lang['Strikethrough'] = 'Acțiunile vor fi tăiate cu o linie când sunt bifate?';
11 | $lang['CheckboxText'] = 'Dacă AllowLinks e dezactivat, apăsarea pe textul acțiunii o va bifa?';
12 | $lang['Checkbox'] = '(Setarea implicită pentru opțiunea "checkbox") Căsuțele de bifat vor fi afișate în cadrul listelor?';
13 | $lang['Header'] = '(Setarea implicită pentru opțiunea "header") Cum va fi numit titlul unei liste? Ca "id" sau ca primul titlu al paginii "firstheader" sau niciun titlu cu "none".';
14 | $lang['Username'] = '(Setarea implicită pentru opțiunea "username") Cum va fi afișat numele utilizatorului responsabil? Ca "username", nume complet "real" sau deloc "none"';
15 | $lang['ShowdateTag'] = '(Setarea implicită pentru opțiunea "showdate") Start/Due-date va fi afișată în definiție?';
16 | $lang['ShowdateList'] = '(Setarea implicită pentru opțiunea "showdate") Start/Due-date va fi afișată în cadrul listelor?';
17 |
--------------------------------------------------------------------------------
/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/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/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/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/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/de/settings.php:
--------------------------------------------------------------------------------
1 | \n
9 | * create german language file \n
10 | *
11 | * @author Leo Eibler
12 | */
13 | $lang['AllowLinks'] = 'Sollen Aufgaben/Todos auch auf Seiten mit dem gleichen Namen verlinken?';
14 | $lang['ActionNamespace'] = 'Wenn AllowLinks aktiv ist, mit welchem Namespace sollen die Aufgaben-Seiten erstellt werden (".:" = Aktueller NS, leer = Root NS)';
15 | $lang['Strikethrough'] = 'Sollen erledigte Aufgaben durchgestrichen werden?';
16 | $lang['CheckboxText'] = 'Wenn AllowLinks nicht aktiv ist, soll dann ein Klick auf die Aufgabe, die Aufgabe als erledigt markieren?';
17 | $lang['Checkbox'] = '(Default Wert für Option "checkbox") Soll in Listen die CheckBox aufscheinen?';
18 | $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"".';
19 | $lang['Username'] = '(Default Wert für Option "username") Wie soll der Username ausgegeben werden? Als "username", voller Name "real" oder garnicht "none"';
20 | $lang['ShowdateTag'] = '(Default Wert für Option "showdate") Soll das Start/Fällig-Datum bei Tag-Definitionen ausgegeben werden?';
21 | $lang['ShowdateList'] = '(Default Wert für Option "showdate") Soll das Start/Fällig-Datum bei Listen ausgegeben werden?';
22 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | });
--------------------------------------------------------------------------------
/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, $this->getLang('uncheckall_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 | echo json_encode($return);
193 | }
194 |
195 | /**
196 | * @brief gets current to-do tag and returns a new one depending on checked
197 | * @param $todoTag string current to-do tag e.g.
198 | * @param $checked int check flag (todo completed=1, todo uncompleted=0)
199 | * @return string new to-do completed or uncompleted tag e.g.
200 | */
201 | private function _buildTodoTag($todoTag, $checked) {
202 | $user = '';
203 | if($checked == 1) {
204 | if(!empty($_SERVER['REMOTE_USER'])) { $user = $_SERVER['REMOTE_USER']; }
205 | $newTag = preg_replace('/>/', ' #'.$user.':'.date('Y-m-d').'>', $todoTag);
206 | } else {
207 | $newTag = preg_replace('/[\s]*[#].*>/', '>', $todoTag);
208 | }
209 | return $newTag;
210 | }
211 |
212 | /**
213 | * Find position of $occurance-th $needle in haystack
214 | */
215 | private function _strnpos($haystack, $needle, $occurance, $pos = 0) {
216 | for($i = 1; $i <= $occurance; $i++) {
217 | $pos = strpos($haystack, $needle, $pos);
218 |
219 | if ($pos===false) {return false; }
220 |
221 | $pos++;
222 | }
223 | return $pos - 1;
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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'] != '*') { //date comparison is needed unless we don't care -> '*'
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 | } elseif($data['startignore'] != '!') { //start date not set and we're not looking for todos without start date.
445 | $condition3s = false;
446 | }
447 | }
448 |
449 | if(isset($data['duebefore']) || isset($data['dueafter']) || isset($data['dueat'])) {
450 | if(isset($data['due'])) {
451 | if($data['dueignore'] != '*') { //date comparison is needed unless we don't care -> '*'
452 | if(isset($data['duebefore'])) { $condition3d = $condition3d && new DateTime($data['duebefore']) > $data['due']; }
453 | if(isset($data['dueafter'])) { $condition3d = $condition3d && new DateTime($data['dueafter']) < $data['due']; }
454 | if(isset($data['dueat'])) { $condition3d = $condition3d && new DateTime($data['dueat']) == $data['due']; }
455 | }
456 | } elseif($data['dueignore'] != '!') { //due date not set and we're not looking for todos without due date.
457 | $condition3d = false;
458 | }
459 | }
460 | $condition3 = $condition3s && $condition3d;
461 | }
462 |
463 | // compare completed date
464 | $condition4 = true;
465 | if(isset($data['completedbefore'])) {
466 | $condition4 = $condition4 && new DateTime($data['completedbefore']) > $data['completeddate'];
467 | }
468 | if(isset($data['completedafter'])) {
469 | $condition4 = $condition4 && new DateTime($data['completedafter']) < $data['completeddate'];
470 | }
471 | if(isset($data['completedat'])) {
472 | $condition4 = $condition4 && new DateTime($data['completedat']) == $data['completeddate'];
473 | }
474 |
475 | return $condition1 AND $condition2 AND $condition3 AND $condition4;
476 | }
477 |
478 |
479 | /**
480 | * Analyse of relative/absolute Date and return an absolute date
481 | *
482 | * @param $date string absolute/relative value of the date to analyse
483 | * @return array absolute date or actual date if $date is invalid
484 | */
485 | private function analyseDate($date) {
486 | $result = array($date, '');
487 | if(is_string($date)) {
488 | if($date == '!') {
489 | $result = array('', '!');
490 | } elseif ($date =='*') {
491 | $result = array('', '*');
492 | } else {
493 | if(substr($date, -1) == '!') {
494 | $date = substr($date, 0, -1);
495 | $result = array($date, '!');
496 | }
497 |
498 | if(date('Y-m-d', strtotime($date)) == $date) {
499 | $result[0] = $date;
500 | } elseif(preg_match('/^[\+\-]\d+$/', $date)) { // check if we have a valid relative value
501 | $newdate = date_create(date('Y-m-d'));
502 | date_modify($newdate, $date . ' day');
503 | $result[0] = date_format($newdate, 'Y-m-d');
504 | } else {
505 | $result[0] = date('Y-m-d');
506 | }
507 | }
508 | } else { $result[0] = date('Y-m-d'); }
509 |
510 | return $result;
511 | }
512 |
513 |
514 | }
515 |
--------------------------------------------------------------------------------