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"><< '.$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').' >></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