1<?php
2/**
3 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
4 * @author     Esther Brunner <wikidesign@gmail.com>
5 */
6
7use dokuwiki\plugin\blogtng\entities\Comment;
8
9/**
10 * Class admin_plugin_blogtng
11 */
12class admin_plugin_blogtng extends DokuWiki_Admin_Plugin {
13
14    /** @var helper_plugin_blogtng_comments */
15    protected $commenthelper = null;
16    /** @var helper_plugin_blogtng_entry */
17    protected $entryhelper   = null;
18    /** @var helper_plugin_blogtng_sqlite */
19    protected $sqlitehelper  = null;
20    /** @var helper_plugin_blogtng_tags */
21    protected $taghelper     = null;
22    /**
23     * @var string action in admin to perform
24     */
25    private $admin;
26    /**
27     * @var int number of comments per page of the comment list
28     */
29    private $commentLimit;
30    /**
31     * @var int number of entries per page of the entry list
32     */
33    private $entryLimit;
34
35    /**
36     * Determine position in list in admin window
37     * Lower values are sorted up
38     *
39     * @return int
40     */
41    public function getMenuSort() { return 200; }
42
43    /**
44     * Return true for access only by admins (config:superuser) or false if managers are allowed as well
45     *
46     * @return bool
47     */
48    public function forAdminOnly() { return false; }
49
50    /**
51     * Constructor
52     */
53    public function __construct() {
54        $this->commenthelper = plugin_load('helper', 'blogtng_comments');
55        $this->entryhelper   = plugin_load('helper', 'blogtng_entry');
56        $this->sqlitehelper  = plugin_load('helper', 'blogtng_sqlite');
57        $this->taghelper     = plugin_load('helper', 'blogtng_tags');
58    }
59
60    /**
61     * Handles all actions of the admin component
62     *
63     * @author Michael Klier <chi@chimeric.de>
64     */
65    public function handle() {
66        global $INPUT;
67        if($INPUT->has('admin')) {
68            $this->admin = $INPUT->extract('admin')->str('admin'); //can be post or get
69        } else {
70            $this->admin = '';
71        }
72        //skip actions when no valid security token given
73        $noSecTokenNeeded = ['search', 'comment_edit', 'comment_preview', null];
74        if(!in_array($this->admin, $noSecTokenNeeded) && !checkSecurityToken()) {
75            $this->admin = '';
76        }
77
78        //get some preferences
79        $this->commentLimit = (int) get_doku_pref('blogtng_comment_limit', 15);
80        $this->entryLimit = (int) get_doku_pref('blogtng_entry_limit', 5);
81
82
83        // handle actions
84        switch($this->admin) {
85
86            case 'comment_save':
87                // FIXME error handling?
88                $comment = $this->getPostedComment();
89
90                $this->commenthelper->save($comment);
91                msg($this->getLang('msg_comment_save'), 1);
92                break;
93
94            case 'comment_delete':
95                // FIXME error handling
96                $this->commenthelper->delete($INPUT->post->str('comment-cid'));
97                msg($this->getLang('msg_comment_delete'), 1);
98                break;
99
100            case 'comment_batch_edit':
101                $batch = $INPUT->post->str('comment_batch_edit');
102                $cids  = $INPUT->post->arr('comments-cids');
103                if($cids) {
104                    foreach($cids as $cid) {
105                        switch($batch) {
106                            // FIXME messages
107                            case 'delete':
108                                $this->commenthelper->delete($cid);
109                                msg($this->getLang('msg_comment_delete'), 1);
110                                break;
111                            case 'status_hidden':
112                                $this->commenthelper->moderate($cid, 'hidden');
113                                msg($this->getLang('msg_comment_status_change'), 1);
114                                break;
115                            case 'status_visible':
116                                $this->commenthelper->moderate($cid, 'visible');
117                                msg($this->getLang('msg_comment_status_change'), 1);
118                                break;
119                        }
120                    }
121                }
122                //back to default view
123                $this->admin = '';
124                break;
125
126            case 'entry_set_blog':
127                // FIXME errors?
128                $pid = $INPUT->post->str('entry-pid');
129                $blog = $INPUT->post->str('entry-blog');
130                if($pid) {
131                    $blogs = $this->entryhelper->getAllBlogs();
132                    if(in_array($blog, $blogs)) {
133                        $this->entryhelper->load_by_pid($pid);
134                        $this->entryhelper->entry['blog'] = $blog;
135                        $this->entryhelper->save();
136                    }
137                }
138                msg($this->getLang('msg_entry_blog_change'), 1);
139                break;
140
141            case 'entry_set_commentstatus':
142                $pid = $INPUT->post->str('entry-pid');
143                $status = $INPUT->post->str('entry-commentstatus');
144                if($pid) {
145                    if(in_array($status, ['disabled', 'enabled', 'closed'])) {
146                        $this->entryhelper->load_by_pid($pid);
147                        $this->entryhelper->entry['commentstatus'] = $status;
148                        $this->entryhelper->save();
149                    }
150                }
151                msg($this->getLang('msg_comment_status_change'), 1);
152                break;
153
154            case 'comment_edit':
155            case 'comment_preview':
156            case 'search':
157            default:
158                // do nothing - show dashboard
159
160                //update preferences
161                if ($INPUT->has('comment-limit')) {
162                    $this->commentLimit = $INPUT->int('comment-limit');
163                    set_doku_pref('blogtng_comment_limit', $this->commentLimit);
164                }
165
166                if ($INPUT->has('entry-limit')) {
167                    $this->entryLimit = $INPUT->int('entry-limit');
168                    set_doku_pref('blogtng_entry_limit', $this->entryLimit);
169                }
170                break;
171        }
172    }
173
174    /**
175     * Handles the XHTML output of the admin component
176     *
177     * @author Michael Klier <chi@chimeric.de>
178     * @author hArpanet <dokuwiki-blogtng@harpanet.com>
179     */
180    public function html() {
181        global $ID, $INPUT;
182
183        ptln('<h1>'.$this->getLang('menu').'</h1>');
184
185        ptln('<div id="blogtng__admin">');
186
187        // display link back to dashboard
188        if($this->admin) {
189            ptln('<div class="level1">');
190            $params = ['do'=>'admin', 'page'=>'blogtng'];
191            ptln('<p><a href="' . wl($ID, $params) . '" title="' . $this->getLang('dashboard') . '">&larr; ' . $this->getLang('dashboard') . '</a></p>');
192            ptln('</div>');
193
194        }
195
196        switch($this->admin) {
197            case 'search':
198                // display search form
199                $this->htmlSearchForm();
200
201                ptln('<h2>' . $this->getLang('searchresults') . '</h2>');
202
203                $query = $INPUT->arr('query');
204                $query['resultset'] = 'query';
205                $query['limit'] = 20;
206                $query['offset'] = $INPUT->int('query-offset');
207                $this->htmlSearchResults($query);
208                break;
209
210            case 'comment_edit':
211            case 'comment_preview':
212                if($this->admin == 'comment_edit') {
213                    $comment = $this->commenthelper->comment_by_cid($INPUT->get->str('comment-cid'));
214                    if($comment !== null) {
215                        $this->htmlCommentEditForm($comment);
216                    }
217                }
218                if($this->admin == 'comment_preview') {
219                    $comment = $this->getPostedComment();
220
221                    $this->htmlCommentEditForm($comment);
222                    $this->htmlCommentPreview($comment);
223                }
224                break;
225
226            default:
227                // display search form
228                $this->htmlSearchForm();
229
230                // print latest 'x' comments/entries
231                $query['limit'] = $this->commentLimit;
232                $query['offset'] = $INPUT->int('comment-offset');
233                $query['resultset'] = 'comment';
234                $this->htmlLatestItems($query);
235
236                $query['limit'] = $this->entryLimit;
237                $query['offset'] = $INPUT->int('entry-offset');
238                $query['resultset']  = 'entry';
239                $this->htmlLatestItems($query);
240                break;
241        }
242
243        ptln('</div>');
244    }
245
246    /**
247     * Displays a list of comments or entries for a given search term
248     *
249     * @param array $query url parameters for query
250     */
251    private function htmlSearchResults($query) {
252        if(!$this->sqlitehelper->ready()) return;
253
254        $db = $this->sqlitehelper->getDB();
255
256        switch($query['filter']) {
257            case 'entry_title':
258            case 'entry_author':
259                $select  = 'SELECT * ';
260                $from    = 'FROM entries ';
261                $orderby = 'ORDER BY created DESC ';
262                $itemdisplaycallback = 'htmlEntryList';
263                break;
264            case 'comment':
265            case 'comment_ip':
266                $select = 'SELECT cid, comments.pid as pid, ip, source, name, comments.mail as mail, web, avatar, comments.created as created, text, status ';
267                $from   = 'FROM comments LEFT JOIN entries ON comments.pid = entries.pid ';
268                $orderby = 'ORDER BY comments.created DESC ';
269                $itemdisplaycallback = 'htmlCommentList';
270            break;
271            case 'tags':
272                $select = 'SELECT DISTINCT entries.pid as pid, page, title, blog, image, created, lastmod, author, login, mail ';
273                $from   = 'FROM entries  LEFT JOIN tags ON entries.pid = tags.pid ';
274                $orderby = 'ORDER BY created DESC ';
275                $itemdisplaycallback = 'htmlEntryList';
276                break;
277            default:
278                return;
279        }
280        $count  = 'SELECT COUNT(*) as count ';
281
282        if(isset($query['blog']) && $query['blog']) {
283            $where = 'WHERE blog = ' . $db->quote_string($query['blog']) . ' ';
284        } else {
285            $where = 'WHERE blog != "" ';
286        }
287
288        if(isset($query['string']) && $query['string'] != '') {
289            switch($query['filter']) {
290                case 'entry_title':
291                    $where .= 'AND ( title LIKE \'%'.$db->escape_string($query['string']).'%\' ) ';
292                    break;
293                case 'entry_author':
294                    $where .= 'AND ( author LIKE \'%'.$db->escape_string($query['string']).'%\' ) ';
295                    break;
296                case 'comment':
297                    $where .= 'AND ( comments.text LIKE \'%'.$db->escape_string($query['string']).'%\' ) ';
298                    break;
299                case 'comment_ip':
300                    $where .= 'AND ( comments.ip LIKE \'%'.$db->escape_string($query['string']).'%\' ) ';
301                    break;
302                case 'tags':
303                    $where .= 'AND ( tags.tag LIKE \'%'.$db->escape_string($query['string']).'%\' ) ';
304                    break;
305            }
306        }
307
308        //comments: if pid is given limit to give page
309        if(isset($query['pid']) && $query['pid'] != '') {
310            $where .= 'AND ( comments.pid = ' . $db->quote_string($query['pid']) . ' ) ';
311        }
312
313        $sqlcount  = $count . $from . $where;
314        $sqlselect = $select . $from . $where . $orderby;
315
316
317        $sqlselect .= ' LIMIT '.$query['limit'];
318        if($query['offset'] > 0) {
319            $sqlselect .= ' OFFSET '.$query['offset'];
320        }
321
322        $res = $db->query($sqlcount);
323        $count = $db->res2single($res);
324
325        $resid = $db->query($sqlselect);
326        if($resid) {
327            $this->htmlShowPaginatedResult($resid, $query, $itemdisplaycallback, $count, $query['limit']);
328        }
329    }
330
331    /**
332     * Display paginated results
333     *
334     * @param object $resid    Database resource object
335     * @param array  $query    Query parameters
336     * @param string $itemdisplaycallback called for each item, to display content of item
337     * @param int    $count    Number of total items
338     * @param int    $limit    Number of results to display per page (page size)
339     *
340     * @author Michael Klier <chi@chimeric.de>
341     * @author hArpanet <dokuwiki-blogtng@harpanet.com>
342     */
343    private function htmlShowPaginatedResult($resid, $query, $itemdisplaycallback, $count, $limit) {
344        global $lang;
345        if(!$resid) return;
346
347        $currentpage   =  floor($query['offset'] / $limit) + 1;
348
349        $items = $this->sqlitehelper->getDB()->res2arr($resid);
350
351        if($items) {
352            ptln('<div class="level2"><p><strong>' . $this->getLang('numhits') . ':</strong> ' . $count .'</p></div>');
353
354            // show pagination only when enough items
355            if($count > $limit) {
356                $this->htmlPagination($query, $currentpage, $count, $limit);
357            }
358            // show list of items using callback
359            call_user_func(array($this, $itemdisplaycallback), $items, $query);
360
361        } else {
362            ptln('<div class="level2">');
363            ptln($lang['nothingfound']);
364            ptln('</div>');
365        }
366
367        // show pagination only when enough items
368        if($count > $limit) {
369            $this->htmlPagination($query, $currentpage, $count, $limit);
370        }
371    }
372
373    /**
374     * Diplays that pagination links of a query
375     *
376     * @param array  $query       Query parameters
377     * @param int    $currentpage number of current page
378     * @param int    $maximum     maximum number of items available
379     * @param int    $limit       number of items per page
380     *
381     * @author Michael Klier <chi@chimeric.de>
382     */
383    private function htmlPagination($query, $currentpage, $maximum, $limit) {
384        $lastpage = (int) ceil($maximum / $limit);
385
386        $pages[] = 1;             // first always
387        $pages[] = $lastpage;     // last page always
388        $pages[] = $currentpage;  // current page always
389
390        if($lastpage > 1){            // if enough pages
391            $pages[] = 2;             // second and ..
392            $pages[] = $lastpage-1;   // one before last
393        }
394
395        // three around current
396        if($currentpage-1 > 0) $pages[] = $currentpage-1;
397        if($currentpage-2 > 0) $pages[] = $currentpage-2;
398        if($currentpage-3 > 0) $pages[] = $currentpage-3;
399        if($currentpage+1 < $lastpage) $pages[] = $currentpage+1;
400        if($currentpage+2 < $lastpage) $pages[] = $currentpage+2;
401        if($currentpage+3 < $lastpage) $pages[] = $currentpage+3;
402
403        $pages = array_unique($pages);
404        sort($pages);
405
406        ptln('<div class="level2"><p>');
407
408        if($currentpage > 1) {
409            $this->htmlPaginationurl($query, ($currentpage - 2) * $limit, '&laquo;', $currentpage - 1);
410        }
411
412        $last = 0;
413        foreach($pages as $page) {
414            if($page - $last > 1) {
415                ptln('<span class="sep">...</span>');
416            }
417            if($page == $currentpage) {
418                ptln('<span class="cur">' . $page . '</span>');
419            } else {
420                $this->htmlPaginationurl($query, ($page - 1) * $limit, $page, $page);
421            }
422            $last = $page;
423        }
424
425        if($currentpage < $lastpage) {
426            $this->htmlPaginationurl($query, $currentpage * $limit, '&raquo;', $currentpage + 1);
427        }
428
429        ptln('</p></div>');
430    }
431
432    /**
433     * Print a pagination link.
434     *
435     * @param array   $query   Query parameters
436     * @param int     $offset  number of previous items
437     * @param string  $text    text of url
438     * @param string  $title   title of url
439     * @internal param string $anchor url anchor
440     */
441    private function htmlPaginationurl($query, $offset, $text, $title) {
442        global $ID;
443        list($params, $anchor) = $this->buildUrlParams($query, $offset);
444        ptln("<a href='".wl($ID, $params).'#'.$anchor."' title='$title'>$text</a>");
445    }
446
447    /**
448     * Build URL parameters.
449     *
450     * @param array  $query   Query parameters
451     * @param int    $offset  number of previous items
452     * @return array($params, $anchor)
453     */
454    private function buildUrlParams($query, $offset) {
455        $params = [
456            'do' => 'admin',
457            'page' => 'blogtng',
458            $query['resultset'].'-offset' => $offset
459        ];
460        $anchor = $query['resultset'] . '_latest';
461
462        if($query['resultset'] == 'query') {
463            $params = $params + [
464                    'admin' => 'search',
465                    'query[filter]' => $query['filter'],
466                    'query[blog]'   => $query['blog'],
467                    'query[string]' => $query['string'],
468                    'query[pid]'    => $query['pid']
469                ];
470            $anchor = '';
471        }
472        return [$params, $anchor];
473    }
474
475    /**
476     * Display the latest comments or entries
477     *
478     * @param array $query parameters
479     *
480     * @author Michael Klier <chi@chimeric.de>
481     * @author hArpanet <dokuwiki-blogtng@harpanet.com>
482     */
483    private function htmlLatestItems($query) {
484        $resultset = $query['resultset']; //query,comment,entry
485
486        printf("<h2 id='{$resultset}_latest'>".$this->getLang($resultset.'_latest').'</h2>', $query['limit']);
487        $this->htmlLimitForm($query);
488
489        if(!$this->sqlitehelper->ready()) return;
490
491        $count = 'SELECT COUNT(pid) as count ';
492        $select = 'SELECT * ';
493
494        if($resultset == 'entry') {
495            $from = 'FROM entries ';
496            $where = 'WHERE blog != "" ';
497            $itemdisplaycallback = 'htmlEntryList';
498        } else {
499            $from = 'FROM comments ';
500            $where = '';
501            $itemdisplaycallback = 'htmlCommentList';
502        }
503
504        $orderby = 'ORDER BY created DESC ';
505
506        $sqlcount = $count . $from . $where;
507        $sqlselect = $select . $from . $where . $orderby;
508
509        $limit = $query['limit'];
510        $sqlselect .= 'LIMIT ' . $limit;
511        if($query['offset']) {
512            $sqlselect .= ' OFFSET ' . $query['offset'];
513        }
514
515        $res = $this->sqlitehelper->getDB()->query($sqlcount);
516        $count = $this->sqlitehelper->getDB()->res2single($res);
517
518        $resid = $this->sqlitehelper->getDB()->query($sqlselect);
519        if(!$resid) {
520            return;
521        }
522        $this->htmlShowPaginatedResult($resid, $query, $itemdisplaycallback, $count, $limit);
523    }
524
525    /**
526     * Displays a list of entries, as callback of htmlSearchResults()
527     *
528     * @param array $entries of entries
529     * @param array $query parameters
530     *
531     * @author Michael Klier <chi@chimeric.de>
532     */
533    private function htmlEntryList($entries, $query) {
534        ptln('<div class="level2">');
535        ptln('<table class="inline">');
536
537        ptln('<th>' . $this->getLang('created') . '</th>');
538        ptln('<th>' . $this->getLang('author') . '</th>');
539        ptln('<th>' . $this->getLang('entry') . '</th>');
540        ptln('<th>' . $this->getLang('blog') . '</th>');
541        ptln('<th>' . $this->getLang('commentstatus') . '</th>');
542        ptln('<th>' . $this->getLang('comments') . '</th>');
543        ptln('<th>' . $this->getLang('tags') . '</th>');
544        ptln('<th></th>');
545        foreach($entries as $entry) {
546            $this->htmlEntryItem($entry, $query);
547        }
548        ptln('</table>');
549        ptln('</div>');
550    }
551
552    /**
553     * Displays a single entry and related actions
554     *
555     * @param array $entry Single entry
556     * @param array $query Query parameters
557     *
558     * @author Michael Klier <chi@chimeric.de>
559     */
560    private function htmlEntryItem($entry, $query) {
561        global $lang;
562        global $ID;
563
564        static $class = 'odd';
565        ptln('<tr class="' . $class . '">');
566        $class = ($class == 'odd') ? 'even' : 'odd';
567
568        ptln('<td class="entry_created">' . dformat($entry['created']) . '</td>');
569        ptln('<td class="entry_author">' . hsc($entry['author']) . '</td>');
570        ptln('<td class="entry_title">' . html_wikilink(':'.$entry['page'], $entry['title']) . '</td>');
571        ptln('<td class="entry_set_blog">' . $this->htmlEntryEditForm($entry, $query, 'blog') . '</th>');
572        ptln('<td class="entry_set_commentstatus">' . $this->htmlEntryEditForm($entry, $query, 'commentstatus') . '</th>');
573
574        $this->commenthelper->setPid($entry['pid']);
575
576        // search comments of this entry link
577        ptln('<td class="entry_comments">');
578        $count = $this->commenthelper->get_count(null, true);
579        if($count > 0) {
580            $params = array('do' => 'admin',
581                            'page' => 'blogtng',
582                            'admin' => 'search',
583                            'query[filter]' => 'comment',
584                            'query[pid]' => $entry['pid']);
585            ptln('<a href="' . wl($ID, $params) . '" title="' . $this->getLang('comments') . '">' . $count . '</a>');
586        } else {
587            ptln($count);
588        }
589        ptln('</td>');
590
591        // tags filter links
592        ptln('<td class="entry_tags">');
593        $this->taghelper->load($entry['pid']);
594        $tags = $this->taghelper->getTags();
595        $count = count($tags);
596        for($i = 0; $i < $count; $i++) {
597            $params = array('do' => 'admin',
598                            'page' => 'blogtng',
599                            'admin' => 'search',
600                            'query[filter]' => 'tags',
601                            'query[string]' => $tags[$i]);
602            $link = '<a href="' . wl($ID, $params) . '" title="' . $tags[$i] . '">' . $tags[$i] . '</a>';
603            if($i < ($count - 1)) $link .= ', ';
604            ptln($link);
605        }
606        ptln('</td>');
607
608        // edit links
609        ptln('<td class="entry_edit">');
610        $params = array('id' => $entry['page'],
611                        'do' => 'edit');
612        ptln('<a href="' . wl($ID, $params) . '" class="blogtng_btn_edit" title="' . $lang['btn_secedit'] . '">' . $lang['btn_secedit'] . '</a>');
613        ptln('</td>');
614
615        ptln('</tr>');
616    }
617
618    /**
619     * Displays a list of comments, as callback of htmlSearchResults()
620     *
621     * @param array[] $comments List of comments
622     * @param array $query    Query parameters
623     *
624     * @author Michael Klier <chi@chimeric.de>
625     * @author hArpanet <dokuwiki-blogtng@harpanet.com>
626     */
627    private function htmlCommentList($comments, $query) {
628        global $lang;
629
630        ptln('<div class="level2">');
631
632        ptln('<form action="' . DOKU_SCRIPT . '" method="post" id="blogtng__comment_batch_edit_form">');
633        ptln('<input type="hidden" name="page" value="blogtng" />');
634        ptln('<input type="hidden" name="comment-offset" value="' .$query['offset']. '" />');
635        ptln('<input type="hidden" name="sectok" value="' .getSecurityToken(). '" />');
636        ptln('<input type="hidden" name="admin" value="comment_batch_edit" />');
637
638        ptln('<table class="inline">');
639
640        ptln('<th id="blogtng__admin_checkall_th"></th>');
641        ptln('<th>' . $this->getLang('comments') . '</th>');
642        ptln('<th></th>');
643
644        foreach($comments as $comment) {
645            $this->htmlCommentItem(new Comment($comment));
646        }
647
648        ptln('</table>');
649
650        ptln('<select name="comment_batch_edit">');
651            ptln('<option value="status_visible">Visible</option>');
652            ptln('<option value="status_hidden">Hidden</option>');
653            ptln('<option value="delete">'.$lang['btn_delete'].'</option>');
654        ptln('</select>');
655        ptln('<input type="submit" class="edit button" name="do[admin]" value="' . $lang['btn_update'] . '" />');
656        ptln('</form>');
657
658        ptln('</div>');
659    }
660
661    /**
662     * Displays a single comment and related actions
663     *
664     * @param Comment $comment A single comment
665     *
666     * @author Michael Klier <chi@chimeric.de>
667     * @author hArpanet <dokuwiki-blogtng@harpanet.com>
668     */
669    private function htmlCommentItem($comment) {
670        global $lang;
671        global $ID;
672
673        static $class = 'odd';
674        ptln('<tr class="' . $class . '">');
675        $class = ($class == 'odd') ? 'even' : 'odd';
676
677        $cmt = new Comment($comment);
678        ptln('<td class="admin_checkbox">');
679            ptln('<input type="checkbox" class="comment_cid" name="comments-cids[]" value="' . $comment->getCid() . '" />');
680        ptln('</td>');
681
682        ptln('<td class="comment_row">');
683        ptln('<div class="comment_text" title="'.$this->getLang('comment_text').'">' . hsc($comment->getText()) . '</div>');
684        ptln('<div class="comment_metadata">');
685            ptln('<span class="comment_created" title="'.$this->getLang('created').'">' . dformat($comment->getCreated()) . '</span>');
686            ptln('<span class="comment_ip" title="'.$this->getLang('comment_ip').'">' . hsc($comment->getIp()) . '</span>');
687
688            ptln('<span class="comment_name" title="'.$this->getLang('comment_name').'">');
689                $avatar = $cmt->tpl_avatar(16,16,true);
690                if($avatar) ptln('<img src="' . $avatar . '" alt="' . hsc($comment->getName()) . '" class="avatar" /> ');
691                if($comment->getMail()){
692                    ptln('<a href="mailto:' . hsc($comment->getMail()) . '" class="mail" title="' . hsc($comment->getMail()) . '">' . hsc($comment->getName()) . '</a>');
693                }else{
694                    ptln(hsc($comment->getName()));
695                }
696            ptln('</span>');
697
698            $weburl = '';
699            if($comment->getWeb()) {
700                $weburl = '<a href="' . hsc($comment->getWeb()) . '" title="' . hsc($comment->getWeb()) . '">' . hsc($comment->getWeb()) . '</a>';
701            }
702            ptln('<span class="comment_web" title="'.$this->getLang('comment_web').'">'.$weburl.'</span>');
703
704            ptln('<span class="comment_status" title="'.$this->getLang('comment_status').'">' . hsc($comment->getStatus()) . '</span>');
705            ptln('<span class="comment_source" title="'.$this->getLang('comment_source').'">' . hsc($comment->getSource()) . '</span>');
706
707            $this->entryhelper->load_by_pid($comment->getPid());
708            $pagelink = html_wikilink(':'.$this->entryhelper->entry['page'], $this->entryhelper->entry['title']);
709            ptln('<span class="comment_entry" title="'.$this->getLang('comment_entry').'">' . $pagelink . '</span>');
710        ptln('</div>');
711        ptln('</td>');
712
713        ptln('<td class="comment_edit">');
714            $params = array('do'=>'admin',
715                            'page'=>'blogtng',
716                            'comment-cid'=>$comment->getCid(),
717                            'admin'=>'comment_edit');
718            ptln('<a href="' . wl($ID, $params). '" class="blogtng_btn_edit" title="' . $lang['btn_edit'] . '">' . $lang['btn_secedit'] . '</a>');
719        ptln('</td>');
720
721        ptln('</tr>');
722    }
723
724    /**
725     * Displays a preview of the comment
726     *
727     * @param Comment $comment submitted comment
728     */
729    private function htmlCommentPreview($comment) {
730        $this->entryhelper->load_by_pid($comment->getPid());
731        $blogname = $this->entryhelper->get_blog();
732
733        ptln('<div id="blogtng__comment_preview">');
734        ptln(p_locale_xhtml('preview'));
735        ptln('<br />');
736        $comment->output($blogname);
737        ptln('</div>');
738    }
739
740    /**
741     * Displays the form to change the comment status of a blog entry
742     *
743     * @param array $entry Blog entry
744     * @param array $query Query parameters
745     * @param string $field 'blog' or 'commentstatus'
746     * @return false|string
747     *
748     * @author Michael Klier <chi@chimeric.de>
749     * @author hArpanet <dokuwiki-blogtng@harpanet.com>
750     */
751    private function htmlEntryEditForm($entry, $query, $field = 'commentstatus') {
752        global $lang;
753
754        $changablefields = ['commentstatus', 'blog'];
755        if(!in_array($field, $changablefields)) return hsc($entry[$field]);
756
757        $form = new Doku_Form(['id'=>"blogtng__entry_set_{$field}_form"]);
758        $form->addHidden('do', 'admin');
759        $form->addHidden('page', 'blogtng');
760        $form->addHidden('entry-pid', $entry['pid']);
761        $form->addHidden('entry-offset', $query['offset']);
762
763        if($field == 'commentstatus') {
764            $availableoptions = ['enabled', 'disabled', 'closed'];
765        } else {
766            $availableoptions = $this->entryhelper->getAllBlogs();
767        }
768        $form->addElement(form_makeListBoxField("entry-$field", $availableoptions, $entry[$field], ''));
769        $form->addElement('<input type="submit" name="admin[entry_set_'.$field.']" class="edit button" value="' . $lang['btn_update'] . '" />');
770
771        ob_start();
772        html_form("blotng__btn_entry_set_$field", $form);
773        return ob_get_clean();
774    }
775
776    /**
777     * Displays the comment edit form
778     *
779     * @param Comment $comment
780     *
781     * @author Michael Klier <chi@chimeric.de>
782     */
783    private function htmlCommentEditForm($comment) {
784        global $lang;
785
786        ptln('<div class="level1">');
787        $form = new Doku_Form(['id'=>'blogtng__comment_edit_form']);
788        $form->startFieldset($this->getLang('act_comment_edit'));
789        $form->addHidden('page', 'blogtng');
790        $form->addHidden('do', 'admin');
791        $form->addHidden('comment-cid', $comment->getCid());
792        $form->addHidden('comment-pid', $comment->getPid());
793        $form->addHidden('comment-created', $comment->getCreated());
794        $form->addElement(form_makeListBoxField('comment-status', ['visible', 'hidden'], $comment->getStatus(), $this->getLang('comment_status')));
795        $form->addElement('<br />');
796        $form->addElement(form_makeListBoxField('comment-source', ['comment', 'trackback', 'pingback'], $comment->getSource(), $this->getLang('comment_source')));
797        $form->addElement('<br />');
798        $form->addElement(form_makeTextField('comment-name', $comment->getName(), $this->getLang('comment_name')));
799        $form->addElement('<br />');
800        $form->addElement(form_makeTextField('comment-mail', $comment->getMail(), $this->getLang('comment_mail')));
801        $form->addElement('<br />');
802        $form->addElement(form_makeTextField('comment-web', $comment->getWeb(), $this->getLang('comment_web')));
803        $form->addElement('<br />');
804        $form->addElement(form_makeTextField('comment-avatar', $comment->getAvatar(), $this->getLang('comment_avatar')));
805        $form->addElement('<br />');
806        $form->addElement('<textarea class="edit" name="comment-text" rows="10" cols="80">' . $comment->getText() . '</textarea>');
807        $form->addElement('<input type="submit" name="admin[comment_save]" class="edit button" value="' . $lang['btn_save'] . '" />');
808        $form->addElement('<input type="submit" name="admin[comment_preview]" class="edit button" value="' . $lang['btn_preview'] . '" />');
809        $form->addElement('<input type="submit" name="admin[comment_delete]" class="edit button" value="' . $lang['btn_delete'] . '" />');
810        $form->endFieldset();
811        html_form('blogtng__edit_comment', $form);
812        ptln('</div>');
813    }
814
815    /**
816     * Displays the search form
817     *
818     * @author Michael Klier <chi@chimeric.de>
819     */
820    private function htmlSearchForm() {
821        global $lang, $INPUT;
822
823        ptln('<div class="level1">');
824
825        $blogs = $this->entryhelper->getAllBlogs();
826
827        $form = new Doku_Form(array('id'=>'blogtng__search_form'));
828        $form->startFieldset($lang['btn_search']);
829        $form->addHidden('page', 'blogtng');
830        $form->addHidden('admin', 'search');
831
832        $query = $INPUT->arr('query');
833        $form->addElement(form_makeListBoxField('query[blog]', $blogs, $query['blog'], $this->getLang('blog')));
834        $form->addElement(form_makeListBoxField('query[filter]', ['entry_title', 'entry_author', 'comment', 'comment_ip', 'tags'], $query['filter'], $this->getLang('filter')));
835        $form->addElement(form_makeTextField('query[string]', $query['string'],''));
836
837        $form->addElement(form_makeButton('submit', 'admin', $lang['btn_search']));
838        $form->endFieldset();
839        html_form('blogtng__search_form', $form);
840
841        ptln('</div>');
842    }
843
844    /**
845     * Displays the limit selection form
846     *
847     * @param array $query Query parameters
848     *
849     * @author hArpanet <dokuwiki-blogtng@harpanet.com>
850     */
851    private function htmlLimitForm($query) {
852        global $lang;
853
854        $resultset = $query['resultset'];
855
856        ptln('<div class="level1">');
857
858        $form = new Doku_Form(['id'=>'blogtng__'.$resultset.'_limit_form']);
859        $form->startFieldset("");
860        $form->addHidden('page', 'blogtng');
861        $form->addElement(
862                form_makeListBoxField($resultset . '-limit',
863                    [5,10,15,20,25,30,40,50,100],
864                    $query['limit'],
865                    $this->getLang('numhits')));
866        $form->addHidden($resultset . '-offset', $query['offset']);
867        $form->addElement(form_makeButton('submit', 'admin', $lang['btn_update']));
868        $form->endFieldset();
869        html_form('blogtng__'.$resultset.'_cnt_form', $form);
870
871        ptln('</div>');
872    }
873
874    /**
875     * Create a comment with the posted from data
876     *
877     * @return Comment
878     */
879    private function getPostedComment()
880    {
881        global $INPUT;
882
883        $comment = new Comment();
884        $comment->setCid($INPUT->post->str('comment-cid'));
885        $comment->setPid($INPUT->post->str('comment-pid'));
886        $comment->setCreated($INPUT->post->str('comment-created'));
887        $comment->setStatus($INPUT->post->str('comment-status'));
888        $comment->setSource($INPUT->post->str('comment-source'));
889        $comment->setName($INPUT->post->str('comment-name'));
890        $comment->setMail($INPUT->post->str('comment-mail'));
891        $comment->setWeb($INPUT->post->str('comment-web'));
892        $comment->setAvatar($INPUT->post->str('comment-avatar'));
893        $comment->setText($INPUT->post->str('comment-text'));
894        return $comment;
895    }
896}
897