xref: /plugin/discussion/admin.php (revision c3413364e899c9a3fd349480069bbe9b8c803d5a)
1a768ba62Swikidesign<?php
2264b7327Swikidesign/**
3264b7327Swikidesign * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
4264b7327Swikidesign * @author     Esther Brunner <wikidesign@gmail.com>
5264b7327Swikidesign */
6264b7327Swikidesign
7*c3413364SGerrit Uitslaguse dokuwiki\Utf8\PhpString;
8*c3413364SGerrit Uitslag
9de7e6f00SGerrit Uitslag/**
10de7e6f00SGerrit Uitslag * Class admin_plugin_discussion
11de7e6f00SGerrit Uitslag */
12a768ba62Swikidesignclass admin_plugin_discussion extends DokuWiki_Admin_Plugin {
13a768ba62Swikidesign
14de7e6f00SGerrit Uitslag    /**
15de7e6f00SGerrit Uitslag     * @return int
16de7e6f00SGerrit Uitslag     */
17*c3413364SGerrit Uitslag    public function getMenuSort() { return 200; }
18de7e6f00SGerrit Uitslag
19de7e6f00SGerrit Uitslag    /**
20de7e6f00SGerrit Uitslag     * @return bool
21de7e6f00SGerrit Uitslag     */
22*c3413364SGerrit Uitslag    public function forAdminOnly() { return false; }
23a768ba62Swikidesign
24*c3413364SGerrit Uitslag    public function handle() {
25*c3413364SGerrit Uitslag        global $lang, $INPUT;
26573e23a1Swikidesign
27*c3413364SGerrit Uitslag        $cids = $INPUT->post->arr('cid');
28*c3413364SGerrit Uitslag        if (is_array($cids)) {
29*c3413364SGerrit Uitslag            $cids = array_keys($cids);
30*c3413364SGerrit Uitslag        }
31de7e6f00SGerrit Uitslag        /** @var action_plugin_discussion $action */
32*c3413364SGerrit Uitslag        $action = plugin_load('action', 'discussion');
33573e23a1Swikidesign        if (!$action) return; // couldn't load action plugin component
34573e23a1Swikidesign
35*c3413364SGerrit Uitslag        switch ($INPUT->post->str('comment')) {
36573e23a1Swikidesign            case $lang['btn_delete']:
37*c3413364SGerrit Uitslag                $action->save($cids, '');
38573e23a1Swikidesign                break;
39573e23a1Swikidesign
40573e23a1Swikidesign            case $this->getLang('btn_show'):
41*c3413364SGerrit Uitslag                $action->save($cids, '', 'show');
42573e23a1Swikidesign                break;
43573e23a1Swikidesign
44573e23a1Swikidesign            case $this->getLang('btn_hide'):
45*c3413364SGerrit Uitslag                $action->save($cids, '', 'hide');
46573e23a1Swikidesign                break;
47573e23a1Swikidesign
48573e23a1Swikidesign            case $this->getLang('btn_change'):
49*c3413364SGerrit Uitslag                $this->changeStatus($INPUT->post->str('status'));
50573e23a1Swikidesign                break;
51573e23a1Swikidesign        }
52573e23a1Swikidesign    }
53573e23a1Swikidesign
54*c3413364SGerrit Uitslag    public function html() {
55*c3413364SGerrit Uitslag        global $conf, $INPUT;
56264b7327Swikidesign
57*c3413364SGerrit Uitslag        $first = $INPUT->int('first');
58*c3413364SGerrit Uitslag
59*c3413364SGerrit Uitslag        $num = $conf['recent'] ?: 20;
60264b7327Swikidesign
61573e23a1Swikidesign        ptln('<h1>'.$this->getLang('menu').'</h1>');
62264b7327Swikidesign
63*c3413364SGerrit Uitslag        $threads = $this->getThreads();
64573e23a1Swikidesign
65573e23a1Swikidesign        // slice the needed chunk of discussion pages
66*c3413364SGerrit Uitslag        $isMore = count($threads) > ($first + $num);
67573e23a1Swikidesign        $threads = array_slice($threads, $first, $num);
68573e23a1Swikidesign
69264b7327Swikidesign        foreach ($threads as $thread) {
70*c3413364SGerrit Uitslag            $comments = $this->getComments($thread);
71*c3413364SGerrit Uitslag            $this->threadHead($thread);
72573e23a1Swikidesign            if ($comments === false) {
736fd05bf5Swikidesign                ptln('</div>', 6); // class="level2"
74264b7327Swikidesign                continue;
75264b7327Swikidesign            }
76264b7327Swikidesign
776fd05bf5Swikidesign            ptln('<form method="post" action="'.wl($thread['id']).'">', 8);
786fd05bf5Swikidesign            ptln('<div class="no">', 10);
796fd05bf5Swikidesign            ptln('<input type="hidden" name="do" value="admin" />', 10);
806fd05bf5Swikidesign            ptln('<input type="hidden" name="page" value="discussion" />', 10);
81*c3413364SGerrit Uitslag            echo html_buildlist($comments, 'admin_discussion', [$this, 'commentItem'], [$this, 'liComment']);
82*c3413364SGerrit Uitslag            $this->actionButtons();
83264b7327Swikidesign        }
84*c3413364SGerrit Uitslag        $this->browseDiscussionLinks($isMore, $first, $num);
856fd05bf5Swikidesign
86264b7327Swikidesign    }
87264b7327Swikidesign
88264b7327Swikidesign    /**
89573e23a1Swikidesign     * Returns an array of pages with discussion sections, sorted by recent comments
90e7ac9adaSGerrit Uitslag     *
91e7ac9adaSGerrit Uitslag     * @return array
92573e23a1Swikidesign     */
93*c3413364SGerrit Uitslag    protected function getThreads() {
94573e23a1Swikidesign        global $conf;
95573e23a1Swikidesign
96573e23a1Swikidesign        // returns the list of pages in the given namespace and it's subspaces
97*c3413364SGerrit Uitslag        $items = [];
98*c3413364SGerrit Uitslag        search($items, $conf['datadir'], 'search_allpages', []);
99573e23a1Swikidesign
100573e23a1Swikidesign        // add pages with comments to result
101*c3413364SGerrit Uitslag        $result = [];
102573e23a1Swikidesign        foreach ($items as $item) {
103573e23a1Swikidesign            $id = $item['id'];
104573e23a1Swikidesign
105573e23a1Swikidesign            // some checks
106573e23a1Swikidesign            $file = metaFN($id, '.comments');
107573e23a1Swikidesign            if (!@file_exists($file)) continue; // skip if no comments file
108573e23a1Swikidesign
109573e23a1Swikidesign            $date = filemtime($file);
110*c3413364SGerrit Uitslag            $result[] = [
111573e23a1Swikidesign                    'id'   => $id,
112573e23a1Swikidesign                    'file' => $file,
113573e23a1Swikidesign                    'date' => $date,
114*c3413364SGerrit Uitslag            ];
115573e23a1Swikidesign        }
116573e23a1Swikidesign
117573e23a1Swikidesign        // finally sort by time of last comment
118*c3413364SGerrit Uitslag        usort($result, ['admin_plugin_discussion', 'threadCmp']);
119573e23a1Swikidesign
120573e23a1Swikidesign        return $result;
121573e23a1Swikidesign    }
122573e23a1Swikidesign
123573e23a1Swikidesign    /**
12471a0ae95SGina Haeussge     * Callback for comparison of thread data.
12571a0ae95SGina Haeussge     *
12671a0ae95SGina Haeussge     * Used for sorting threads in descending order by date of last comment.
12771a0ae95SGina Haeussge     * If this date happens to be equal for the compared threads, page id
12871a0ae95SGina Haeussge     * is used as second comparison attribute.
129e7ac9adaSGerrit Uitslag     *
130e7ac9adaSGerrit Uitslag     * @param array $a
131e7ac9adaSGerrit Uitslag     * @param array $b
132e7ac9adaSGerrit Uitslag     * @return int
13371a0ae95SGina Haeussge     */
134*c3413364SGerrit Uitslag    protected function threadCmp($a, $b) {
13571a0ae95SGina Haeussge        if ($a['date'] == $b['date']) {
13671a0ae95SGina Haeussge            return strcmp($a['id'], $b['id']);
13771a0ae95SGina Haeussge        }
138*c3413364SGerrit Uitslag        if ($a['date'] < $b['date']) {
139*c3413364SGerrit Uitslag            return 1;
140*c3413364SGerrit Uitslag        } else {
141*c3413364SGerrit Uitslag            return -1;
142*c3413364SGerrit Uitslag        }
14371a0ae95SGina Haeussge    }
14471a0ae95SGina Haeussge
14571a0ae95SGina Haeussge    /**
146573e23a1Swikidesign     * Outputs header, page ID and status of a discussion thread
147e7ac9adaSGerrit Uitslag     *
148e7ac9adaSGerrit Uitslag     * @param array $thread
149e7ac9adaSGerrit Uitslag     * @return bool
150264b7327Swikidesign     */
151*c3413364SGerrit Uitslag    protected function threadHead($thread) {
152573e23a1Swikidesign        $id = $thread['id'];
153573e23a1Swikidesign
154*c3413364SGerrit Uitslag        $labels = [
155573e23a1Swikidesign            0 => $this->getLang('off'),
156573e23a1Swikidesign            1 => $this->getLang('open'),
157573e23a1Swikidesign            2 => $this->getLang('closed')
158*c3413364SGerrit Uitslag        ];
1596fd05bf5Swikidesign        $title = p_get_metadata($id, 'title');
1604cded5e1SGerrit Uitslag        if (!$title) {
1614cded5e1SGerrit Uitslag            $title = $id;
1624cded5e1SGerrit Uitslag        }
1636fd05bf5Swikidesign        ptln('<h2 name="'.$id.'" id="'.$id.'">'.hsc($title).'</h2>', 6);
1646fd05bf5Swikidesign        ptln('<form method="post" action="'.wl($id).'">', 6);
1656fd05bf5Swikidesign        ptln('<div class="mediaright">', 8);
1666fd05bf5Swikidesign        ptln('<input type="hidden" name="do" value="admin" />', 10);
1676fd05bf5Swikidesign        ptln('<input type="hidden" name="page" value="discussion" />', 10);
1686fd05bf5Swikidesign        ptln($this->getLang('status').': <select name="status" size="1">', 10);
169573e23a1Swikidesign        foreach ($labels as $key => $label) {
170*c3413364SGerrit Uitslag            $selected = ($key == $thread['status'] ? ' selected="selected"' : '');
1716fd05bf5Swikidesign            ptln('<option value="'.$key.'"'.$selected.'>'.$label.'</option>', 12);
172573e23a1Swikidesign        }
1736fd05bf5Swikidesign        ptln('</select> ', 10);
17414382ebaSMichael Klier        ptln('<input type="submit" class="button" name="comment" value="'.$this->getLang('btn_change').'" class"button" title="'.$this->getLang('btn_change').'" />', 10);
1756fd05bf5Swikidesign        ptln('</div>', 8);
1766fd05bf5Swikidesign        ptln('</form>', 6);
1776fd05bf5Swikidesign        ptln('<div class="level2">', 6);
1786fd05bf5Swikidesign        ptln('<a href="'.wl($id).'" class="wikilink1">'.$id.'</a> ', 8);
179573e23a1Swikidesign        return true;
180573e23a1Swikidesign    }
181573e23a1Swikidesign
182573e23a1Swikidesign    /**
183573e23a1Swikidesign     * Returns the full comments data for a given wiki page
184e7ac9adaSGerrit Uitslag     *
185*c3413364SGerrit Uitslag     * @param array $thread by reference with:
186*c3413364SGerrit Uitslag     *  'id' => string,
187*c3413364SGerrit Uitslag     *  'file' => string file location of .comments metadata file
188*c3413364SGerrit Uitslag     *  'status' => int
189*c3413364SGerrit Uitslag     *  'number' => int number of visible comments
190*c3413364SGerrit Uitslag     *
191e7ac9adaSGerrit Uitslag     * @return array|bool
192573e23a1Swikidesign     */
193*c3413364SGerrit Uitslag    protected function getComments(&$thread) {
194573e23a1Swikidesign        $id = $thread['id'];
195573e23a1Swikidesign
1964cded5e1SGerrit Uitslag        if (!$thread['file']) {
1974cded5e1SGerrit Uitslag            $thread['file'] = metaFN($id, '.comments');
1984cded5e1SGerrit Uitslag        }
199573e23a1Swikidesign        if (!@file_exists($thread['file'])) return false; // no discussion thread at all
200573e23a1Swikidesign
201573e23a1Swikidesign        $data = unserialize(io_readFile($thread['file'], false));
202573e23a1Swikidesign
203573e23a1Swikidesign        $thread['status'] = $data['status'];
204573e23a1Swikidesign        $thread['number'] = $data['number'];
205573e23a1Swikidesign        if (!$data['status']) return false;   // comments are turned off
2066fd05bf5Swikidesign        if (!$data['comments']) return false; // no comments
207573e23a1Swikidesign
208*c3413364SGerrit Uitslag        $result = [];
209573e23a1Swikidesign        foreach ($data['comments'] as $cid => $comment) {
210*c3413364SGerrit Uitslag            $this->addComment($cid, $data, $result);
211573e23a1Swikidesign        }
212573e23a1Swikidesign
2134cded5e1SGerrit Uitslag        if (empty($result)) {
2144cded5e1SGerrit Uitslag            return false;
2154cded5e1SGerrit Uitslag        } else {
2164cded5e1SGerrit Uitslag            return $result;
2174cded5e1SGerrit Uitslag        }
218573e23a1Swikidesign    }
219573e23a1Swikidesign
220573e23a1Swikidesign    /**
221573e23a1Swikidesign     * Recursive function to add the comment hierarchy to the result
222e7ac9adaSGerrit Uitslag     *
223*c3413364SGerrit Uitslag     * @param string $cid comment id of current comment
224*c3413364SGerrit Uitslag     * @param array  $data array with all comments by reference
225*c3413364SGerrit Uitslag     * @param array  $result array with all comments by reference enhanced with level
226*c3413364SGerrit Uitslag     * @param string $parent comment id of parent or empty
227*c3413364SGerrit Uitslag     * @param int    $level level of current comment, higher is deeper
228573e23a1Swikidesign     */
229*c3413364SGerrit Uitslag    protected function addComment($cid, &$data, &$result, $parent = '', $level = 1) {
230573e23a1Swikidesign        if (!is_array($data['comments'][$cid])) return; // corrupt datatype
231*c3413364SGerrit Uitslag
232573e23a1Swikidesign        $comment = $data['comments'][$cid];
233*c3413364SGerrit Uitslag        // handle only replies to given parent comment
234*c3413364SGerrit Uitslag        if ($comment['parent'] != $parent) return;
235573e23a1Swikidesign
236573e23a1Swikidesign        // okay, add the comment to the result
237573e23a1Swikidesign        $comment['id'] = $cid;
238573e23a1Swikidesign        $comment['level'] = $level;
239573e23a1Swikidesign        $result[] = $comment;
240573e23a1Swikidesign
241573e23a1Swikidesign        // check answers to this comment
242573e23a1Swikidesign        if (count($comment['replies'])) {
243573e23a1Swikidesign            foreach ($comment['replies'] as $rid) {
244*c3413364SGerrit Uitslag                $this->addComment($rid, $data, $result, $cid, $level + 1);
245573e23a1Swikidesign            }
246573e23a1Swikidesign        }
247264b7327Swikidesign    }
248264b7327Swikidesign
249264b7327Swikidesign    /**
250*c3413364SGerrit Uitslag     * Returns html of checkbox and info about a comment item
251e7ac9adaSGerrit Uitslag     *
252*c3413364SGerrit Uitslag     * @param array $comment array with comment data
253*c3413364SGerrit Uitslag     * @return string html of checkbox and info
254264b7327Swikidesign     */
255*c3413364SGerrit Uitslag    public function commentItem($comment) {
256264b7327Swikidesign        global $conf;
257264b7327Swikidesign
258264b7327Swikidesign        // prepare variables
259264b7327Swikidesign        if (is_array($comment['user'])) { // new format
260264b7327Swikidesign            $name    = $comment['user']['name'];
261264b7327Swikidesign            $mail    = $comment['user']['mail'];
262264b7327Swikidesign        } else {                         // old format
263264b7327Swikidesign            $name    = $comment['name'];
264264b7327Swikidesign            $mail    = $comment['mail'];
265264b7327Swikidesign        }
266264b7327Swikidesign        if (is_array($comment['date'])) { // new format
267264b7327Swikidesign            $created  = $comment['date']['created'];
268264b7327Swikidesign        } else {                         // old format
269264b7327Swikidesign            $created  = $comment['date'];
270264b7327Swikidesign        }
271573e23a1Swikidesign        $abstract = preg_replace('/\s+?/', ' ', strip_tags($comment['xhtml']));
272*c3413364SGerrit Uitslag        if (PhpString::strlen($abstract) > 160) {
273*c3413364SGerrit Uitslag            $abstract = PhpString::substr($abstract, 0, 160).'...';
2744cded5e1SGerrit Uitslag        }
275264b7327Swikidesign
276573e23a1Swikidesign        return '<input type="checkbox" name="cid['.$comment['id'].']" value="1" /> '.
277f014bc86SMichael Klier            $this->email($mail, $name, 'email').', '.strftime($conf['dformat'], $created).': '.
278573e23a1Swikidesign            '<span class="abstract">'.$abstract.'</span>';
279264b7327Swikidesign    }
280264b7327Swikidesign
281264b7327Swikidesign    /**
282*c3413364SGerrit Uitslag     * Returns html of list item openings tag
283e7ac9adaSGerrit Uitslag     *
284e7ac9adaSGerrit Uitslag     * @param array $comment
285e7ac9adaSGerrit Uitslag     * @return string
286264b7327Swikidesign     */
287*c3413364SGerrit Uitslag    public function liComment($comment) {
288*c3413364SGerrit Uitslag        $showclass = ($comment['show'] ? '' : ' hidden');
289*c3413364SGerrit Uitslag        return '<li class="level'.$comment['level'].$showclass.'">';
290264b7327Swikidesign    }
291264b7327Swikidesign
292264b7327Swikidesign    /**
293264b7327Swikidesign     * Show buttons to bulk remove, hide or show comments
294264b7327Swikidesign     */
295*c3413364SGerrit Uitslag    protected function actionButtons() {
296264b7327Swikidesign        global $lang;
297264b7327Swikidesign
2986fd05bf5Swikidesign        ptln('<div class="comment_buttons">', 12);
2996fd05bf5Swikidesign        ptln('<input type="submit" name="comment" value="'.$this->getLang('btn_show').'" class="button" title="'.$this->getLang('btn_show').'" />', 14);
3006fd05bf5Swikidesign        ptln('<input type="submit" name="comment" value="'.$this->getLang('btn_hide').'" class="button" title="'.$this->getLang('btn_hide').'" />', 14);
3016fd05bf5Swikidesign        ptln('<input type="submit" name="comment" value="'.$lang['btn_delete'].'" class="button" title="'.$lang['btn_delete'].'" />', 14);
3026fd05bf5Swikidesign        ptln('</div>', 12); // class="comment_buttons"
3036fd05bf5Swikidesign        ptln('</div>', 10); // class="no"
3046fd05bf5Swikidesign        ptln('</form>', 8);
3056fd05bf5Swikidesign        ptln('</div>', 6); // class="level2"
306573e23a1Swikidesign    }
307573e23a1Swikidesign
308573e23a1Swikidesign    /**
309573e23a1Swikidesign     * Displays links to older newer discussions
310e7ac9adaSGerrit Uitslag     *
311*c3413364SGerrit Uitslag     * @param bool $isMore whether there are more pages needed
312*c3413364SGerrit Uitslag     * @param int  $first first entry on this page
313*c3413364SGerrit Uitslag     * @param int  $num number of entries per page
314573e23a1Swikidesign     */
315*c3413364SGerrit Uitslag    protected function browseDiscussionLinks($isMore, $first, $num) {
316573e23a1Swikidesign        global $ID;
317573e23a1Swikidesign
318*c3413364SGerrit Uitslag        if ($first == 0 && !$isMore) return;
3193e02b3ffSwikidesign
320*c3413364SGerrit Uitslag        $params = ['do' => 'admin', 'page' => 'discussion'];
321573e23a1Swikidesign        $last = $first+$num;
3226fd05bf5Swikidesign        ptln('<div class="level1">', 8);
323*c3413364SGerrit Uitslag        $return = '';
324573e23a1Swikidesign        if ($first > 0) {
325573e23a1Swikidesign            $first -= $num;
3264cded5e1SGerrit Uitslag            if ($first < 0) {
3274cded5e1SGerrit Uitslag                $first = 0;
3284cded5e1SGerrit Uitslag            }
329573e23a1Swikidesign            $params['first'] = $first;
3306fd05bf5Swikidesign            ptln('<p class="centeralign">', 8);
331*c3413364SGerrit Uitslag            $return = '<a href="'.wl($ID, $params).'" class="wikilink1">&lt;&lt; '.$this->getLang('newer').'</a>';
332*c3413364SGerrit Uitslag            if ($isMore) {
333*c3413364SGerrit Uitslag                $return .= ' | ';
334573e23a1Swikidesign            } else {
335*c3413364SGerrit Uitslag                ptln($return, 10);
3366fd05bf5Swikidesign                ptln('</p>', 8);
337573e23a1Swikidesign            }
338*c3413364SGerrit Uitslag        } else if ($isMore) {
3396fd05bf5Swikidesign            ptln('<p class="centeralign">', 8);
340573e23a1Swikidesign        }
341*c3413364SGerrit Uitslag        if ($isMore) {
342573e23a1Swikidesign            $params['first'] = $last;
343*c3413364SGerrit Uitslag            $return .= '<a href="'.wl($ID, $params).'" class="wikilink1">'.$this->getLang('older').' &gt;&gt;</a>';
344*c3413364SGerrit Uitslag            ptln($return, 10);
3456fd05bf5Swikidesign            ptln('</p>', 8);
346573e23a1Swikidesign        }
3476fd05bf5Swikidesign        ptln('</div>', 6); // class="level1"
348573e23a1Swikidesign    }
349573e23a1Swikidesign
350573e23a1Swikidesign    /**
351*c3413364SGerrit Uitslag     * Changes the status of a comment section
352e7ac9adaSGerrit Uitslag     *
353*c3413364SGerrit Uitslag     * @param int $new 0=disabled, 1=enabled, 2=closed
354573e23a1Swikidesign     */
355*c3413364SGerrit Uitslag    protected function changeStatus($new) {
356573e23a1Swikidesign        global $ID;
357573e23a1Swikidesign
358573e23a1Swikidesign        // get discussion meta file name
359573e23a1Swikidesign        $file = metaFN($ID, '.comments');
360573e23a1Swikidesign        $data = unserialize(io_readFile($file, false));
361573e23a1Swikidesign
362573e23a1Swikidesign        $old = $data['status'];
363*c3413364SGerrit Uitslag        if ($old == $new) {
364*c3413364SGerrit Uitslag            return;
365*c3413364SGerrit Uitslag        }
366573e23a1Swikidesign
367573e23a1Swikidesign        // save the comment metadata file
368573e23a1Swikidesign        $data['status'] = $new;
369573e23a1Swikidesign        io_saveFile($file, serialize($data));
370573e23a1Swikidesign
371573e23a1Swikidesign        // look for ~~DISCUSSION~~ command in page file and change it accordingly
372*c3413364SGerrit Uitslag        $patterns = ['~~DISCUSSION:off\2~~', '~~DISCUSSION\2~~', '~~DISCUSSION:closed\2~~'];
373573e23a1Swikidesign        $replace = $patterns[$new];
3743e02b3ffSwikidesign        $wiki = preg_replace('/~~DISCUSSION([\w:]*)(\|?.*?)~~/', $replace, rawWiki($ID));
375573e23a1Swikidesign        saveWikiText($ID, $wiki, $this->getLang('statuschanged'), true);
376264b7327Swikidesign    }
3775ef1705fSiLoveiDo}
378