1a768ba62Swikidesign<?php 2264b7327Swikidesign/** 3264b7327Swikidesign * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 4264b7327Swikidesign * @author Esther Brunner <wikidesign@gmail.com> 5264b7327Swikidesign */ 6264b7327Swikidesign 7c3413364SGerrit Uitslaguse dokuwiki\Utf8\PhpString; 8c3413364SGerrit Uitslag 9de7e6f00SGerrit Uitslag/** 10de7e6f00SGerrit Uitslag * Class admin_plugin_discussion 11de7e6f00SGerrit Uitslag */ 12*9699b3e2SGerrit Uitslagclass admin_plugin_discussion extends DokuWiki_Admin_Plugin 13*9699b3e2SGerrit Uitslag{ 14a768ba62Swikidesign 15de7e6f00SGerrit Uitslag /** 16de7e6f00SGerrit Uitslag * @return int 17de7e6f00SGerrit Uitslag */ 18*9699b3e2SGerrit Uitslag public function getMenuSort() 19*9699b3e2SGerrit Uitslag { 20*9699b3e2SGerrit Uitslag return 200; 21*9699b3e2SGerrit Uitslag } 22de7e6f00SGerrit Uitslag 23de7e6f00SGerrit Uitslag /** 24de7e6f00SGerrit Uitslag * @return bool 25de7e6f00SGerrit Uitslag */ 26*9699b3e2SGerrit Uitslag public function forAdminOnly() 27*9699b3e2SGerrit Uitslag { 28*9699b3e2SGerrit Uitslag return false; 29*9699b3e2SGerrit Uitslag } 30a768ba62Swikidesign 31*9699b3e2SGerrit Uitslag public function handle() 32*9699b3e2SGerrit Uitslag { 33c3413364SGerrit Uitslag global $lang, $INPUT; 34573e23a1Swikidesign 35c3413364SGerrit Uitslag $cids = $INPUT->post->arr('cid'); 36c3413364SGerrit Uitslag if (is_array($cids)) { 37c3413364SGerrit Uitslag $cids = array_keys($cids); 38c3413364SGerrit Uitslag } 39de7e6f00SGerrit Uitslag /** @var action_plugin_discussion $action */ 40c3413364SGerrit Uitslag $action = plugin_load('action', 'discussion'); 41573e23a1Swikidesign if (!$action) return; // couldn't load action plugin component 42573e23a1Swikidesign 43c3413364SGerrit Uitslag switch ($INPUT->post->str('comment')) { 44573e23a1Swikidesign case $lang['btn_delete']: 45c3413364SGerrit Uitslag $action->save($cids, ''); 46573e23a1Swikidesign break; 47573e23a1Swikidesign 48573e23a1Swikidesign case $this->getLang('btn_show'): 49c3413364SGerrit Uitslag $action->save($cids, '', 'show'); 50573e23a1Swikidesign break; 51573e23a1Swikidesign 52573e23a1Swikidesign case $this->getLang('btn_hide'): 53c3413364SGerrit Uitslag $action->save($cids, '', 'hide'); 54573e23a1Swikidesign break; 55573e23a1Swikidesign 56573e23a1Swikidesign case $this->getLang('btn_change'): 57c3413364SGerrit Uitslag $this->changeStatus($INPUT->post->str('status')); 58573e23a1Swikidesign break; 59573e23a1Swikidesign } 60573e23a1Swikidesign } 61573e23a1Swikidesign 62*9699b3e2SGerrit Uitslag public function html() 63*9699b3e2SGerrit Uitslag { 64c3413364SGerrit Uitslag global $conf, $INPUT; 65264b7327Swikidesign 66c3413364SGerrit Uitslag $first = $INPUT->int('first'); 67c3413364SGerrit Uitslag 68c3413364SGerrit Uitslag $num = $conf['recent'] ?: 20; 69264b7327Swikidesign 70573e23a1Swikidesign ptln('<h1>' . $this->getLang('menu') . '</h1>'); 71264b7327Swikidesign 72c3413364SGerrit Uitslag $threads = $this->getThreads(); 73573e23a1Swikidesign 74573e23a1Swikidesign // slice the needed chunk of discussion pages 75c3413364SGerrit Uitslag $isMore = count($threads) > ($first + $num); 76573e23a1Swikidesign $threads = array_slice($threads, $first, $num); 77573e23a1Swikidesign 78264b7327Swikidesign foreach ($threads as $thread) { 79c3413364SGerrit Uitslag $comments = $this->getComments($thread); 80c3413364SGerrit Uitslag $this->threadHead($thread); 81573e23a1Swikidesign if ($comments === false) { 826fd05bf5Swikidesign ptln('</div>', 6); // class="level2" 83264b7327Swikidesign continue; 84264b7327Swikidesign } 85264b7327Swikidesign 866fd05bf5Swikidesign ptln('<form method="post" action="' . wl($thread['id']) . '">', 8); 876fd05bf5Swikidesign ptln('<div class="no">', 10); 886fd05bf5Swikidesign ptln('<input type="hidden" name="do" value="admin" />', 10); 896fd05bf5Swikidesign ptln('<input type="hidden" name="page" value="discussion" />', 10); 90c3413364SGerrit Uitslag echo html_buildlist($comments, 'admin_discussion', [$this, 'commentItem'], [$this, 'liComment']); 91c3413364SGerrit Uitslag $this->actionButtons(); 92264b7327Swikidesign } 93c3413364SGerrit Uitslag $this->browseDiscussionLinks($isMore, $first, $num); 946fd05bf5Swikidesign 95264b7327Swikidesign } 96264b7327Swikidesign 97264b7327Swikidesign /** 98573e23a1Swikidesign * Returns an array of pages with discussion sections, sorted by recent comments 99e7ac9adaSGerrit Uitslag * 100e7ac9adaSGerrit Uitslag * @return array 101573e23a1Swikidesign */ 102*9699b3e2SGerrit Uitslag protected function getThreads() 103*9699b3e2SGerrit Uitslag { 104573e23a1Swikidesign global $conf; 105573e23a1Swikidesign 106573e23a1Swikidesign // returns the list of pages in the given namespace and it's subspaces 107c3413364SGerrit Uitslag $items = []; 108c3413364SGerrit Uitslag search($items, $conf['datadir'], 'search_allpages', []); 109573e23a1Swikidesign 110573e23a1Swikidesign // add pages with comments to result 111c3413364SGerrit Uitslag $result = []; 112573e23a1Swikidesign foreach ($items as $item) { 113573e23a1Swikidesign $id = $item['id']; 114573e23a1Swikidesign 115573e23a1Swikidesign // some checks 116573e23a1Swikidesign $file = metaFN($id, '.comments'); 117573e23a1Swikidesign if (!@file_exists($file)) continue; // skip if no comments file 118573e23a1Swikidesign 119573e23a1Swikidesign $date = filemtime($file); 120c3413364SGerrit Uitslag $result[] = [ 121573e23a1Swikidesign 'id' => $id, 122573e23a1Swikidesign 'file' => $file, 123573e23a1Swikidesign 'date' => $date, 124c3413364SGerrit Uitslag ]; 125573e23a1Swikidesign } 126573e23a1Swikidesign 127573e23a1Swikidesign // finally sort by time of last comment 128c3413364SGerrit Uitslag usort($result, ['admin_plugin_discussion', 'threadCmp']); 129573e23a1Swikidesign 130573e23a1Swikidesign return $result; 131573e23a1Swikidesign } 132573e23a1Swikidesign 133573e23a1Swikidesign /** 13471a0ae95SGina Haeussge * Callback for comparison of thread data. 13571a0ae95SGina Haeussge * 13671a0ae95SGina Haeussge * Used for sorting threads in descending order by date of last comment. 13771a0ae95SGina Haeussge * If this date happens to be equal for the compared threads, page id 13871a0ae95SGina Haeussge * is used as second comparison attribute. 139e7ac9adaSGerrit Uitslag * 140e7ac9adaSGerrit Uitslag * @param array $a 141e7ac9adaSGerrit Uitslag * @param array $b 142e7ac9adaSGerrit Uitslag * @return int 14371a0ae95SGina Haeussge */ 144*9699b3e2SGerrit Uitslag protected function threadCmp($a, $b) 145*9699b3e2SGerrit Uitslag { 14671a0ae95SGina Haeussge if ($a['date'] == $b['date']) { 14771a0ae95SGina Haeussge return strcmp($a['id'], $b['id']); 14871a0ae95SGina Haeussge } 149c3413364SGerrit Uitslag if ($a['date'] < $b['date']) { 150c3413364SGerrit Uitslag return 1; 151c3413364SGerrit Uitslag } else { 152c3413364SGerrit Uitslag return -1; 153c3413364SGerrit Uitslag } 15471a0ae95SGina Haeussge } 15571a0ae95SGina Haeussge 15671a0ae95SGina Haeussge /** 157573e23a1Swikidesign * Outputs header, page ID and status of a discussion thread 158e7ac9adaSGerrit Uitslag * 159e7ac9adaSGerrit Uitslag * @param array $thread 160e7ac9adaSGerrit Uitslag * @return bool 161264b7327Swikidesign */ 162*9699b3e2SGerrit Uitslag protected function threadHead($thread) 163*9699b3e2SGerrit Uitslag { 164573e23a1Swikidesign $id = $thread['id']; 165573e23a1Swikidesign 166c3413364SGerrit Uitslag $labels = [ 167573e23a1Swikidesign 0 => $this->getLang('off'), 168573e23a1Swikidesign 1 => $this->getLang('open'), 169573e23a1Swikidesign 2 => $this->getLang('closed') 170c3413364SGerrit Uitslag ]; 1716fd05bf5Swikidesign $title = p_get_metadata($id, 'title'); 1724cded5e1SGerrit Uitslag if (!$title) { 1734cded5e1SGerrit Uitslag $title = $id; 1744cded5e1SGerrit Uitslag } 1756fd05bf5Swikidesign ptln('<h2 name="' . $id . '" id="' . $id . '">' . hsc($title) . '</h2>', 6); 1766fd05bf5Swikidesign ptln('<form method="post" action="' . wl($id) . '">', 6); 1776fd05bf5Swikidesign ptln('<div class="mediaright">', 8); 1786fd05bf5Swikidesign ptln('<input type="hidden" name="do" value="admin" />', 10); 1796fd05bf5Swikidesign ptln('<input type="hidden" name="page" value="discussion" />', 10); 1806fd05bf5Swikidesign ptln($this->getLang('status') . ': <select name="status" size="1">', 10); 181573e23a1Swikidesign foreach ($labels as $key => $label) { 182c3413364SGerrit Uitslag $selected = ($key == $thread['status'] ? ' selected="selected"' : ''); 1836fd05bf5Swikidesign ptln('<option value="' . $key . '"' . $selected . '>' . $label . '</option>', 12); 184573e23a1Swikidesign } 1856fd05bf5Swikidesign ptln('</select> ', 10); 18614382ebaSMichael Klier ptln('<input type="submit" class="button" name="comment" value="' . $this->getLang('btn_change') . '" class"button" title="' . $this->getLang('btn_change') . '" />', 10); 1876fd05bf5Swikidesign ptln('</div>', 8); 1886fd05bf5Swikidesign ptln('</form>', 6); 1896fd05bf5Swikidesign ptln('<div class="level2">', 6); 1906fd05bf5Swikidesign ptln('<a href="' . wl($id) . '" class="wikilink1">' . $id . '</a> ', 8); 191573e23a1Swikidesign return true; 192573e23a1Swikidesign } 193573e23a1Swikidesign 194573e23a1Swikidesign /** 195573e23a1Swikidesign * Returns the full comments data for a given wiki page 196e7ac9adaSGerrit Uitslag * 197c3413364SGerrit Uitslag * @param array $thread by reference with: 198c3413364SGerrit Uitslag * 'id' => string, 199c3413364SGerrit Uitslag * 'file' => string file location of .comments metadata file 200c3413364SGerrit Uitslag * 'status' => int 201c3413364SGerrit Uitslag * 'number' => int number of visible comments 202c3413364SGerrit Uitslag * 203e7ac9adaSGerrit Uitslag * @return array|bool 204573e23a1Swikidesign */ 205*9699b3e2SGerrit Uitslag protected function getComments(&$thread) 206*9699b3e2SGerrit Uitslag { 207573e23a1Swikidesign $id = $thread['id']; 208573e23a1Swikidesign 2094cded5e1SGerrit Uitslag if (!$thread['file']) { 2104cded5e1SGerrit Uitslag $thread['file'] = metaFN($id, '.comments'); 2114cded5e1SGerrit Uitslag } 212573e23a1Swikidesign if (!@file_exists($thread['file'])) return false; // no discussion thread at all 213573e23a1Swikidesign 214573e23a1Swikidesign $data = unserialize(io_readFile($thread['file'], false)); 215573e23a1Swikidesign 216573e23a1Swikidesign $thread['status'] = $data['status']; 217573e23a1Swikidesign $thread['number'] = $data['number']; 218573e23a1Swikidesign if (!$data['status']) return false; // comments are turned off 2196fd05bf5Swikidesign if (!$data['comments']) return false; // no comments 220573e23a1Swikidesign 221c3413364SGerrit Uitslag $result = []; 222573e23a1Swikidesign foreach ($data['comments'] as $cid => $comment) { 223c3413364SGerrit Uitslag $this->addComment($cid, $data, $result); 224573e23a1Swikidesign } 225573e23a1Swikidesign 2264cded5e1SGerrit Uitslag if (empty($result)) { 2274cded5e1SGerrit Uitslag return false; 2284cded5e1SGerrit Uitslag } else { 2294cded5e1SGerrit Uitslag return $result; 2304cded5e1SGerrit Uitslag } 231573e23a1Swikidesign } 232573e23a1Swikidesign 233573e23a1Swikidesign /** 234573e23a1Swikidesign * Recursive function to add the comment hierarchy to the result 235e7ac9adaSGerrit Uitslag * 236c3413364SGerrit Uitslag * @param string $cid comment id of current comment 237c3413364SGerrit Uitslag * @param array $data array with all comments by reference 238c3413364SGerrit Uitslag * @param array $result array with all comments by reference enhanced with level 239c3413364SGerrit Uitslag * @param string $parent comment id of parent or empty 240c3413364SGerrit Uitslag * @param int $level level of current comment, higher is deeper 241573e23a1Swikidesign */ 242*9699b3e2SGerrit Uitslag protected function addComment($cid, &$data, &$result, $parent = '', $level = 1) 243*9699b3e2SGerrit Uitslag { 244573e23a1Swikidesign if (!is_array($data['comments'][$cid])) return; // corrupt datatype 245c3413364SGerrit Uitslag 246573e23a1Swikidesign $comment = $data['comments'][$cid]; 247c3413364SGerrit Uitslag // handle only replies to given parent comment 248c3413364SGerrit Uitslag if ($comment['parent'] != $parent) return; 249573e23a1Swikidesign 250573e23a1Swikidesign // okay, add the comment to the result 251573e23a1Swikidesign $comment['id'] = $cid; 252573e23a1Swikidesign $comment['level'] = $level; 253573e23a1Swikidesign $result[] = $comment; 254573e23a1Swikidesign 255573e23a1Swikidesign // check answers to this comment 256573e23a1Swikidesign if (count($comment['replies'])) { 257573e23a1Swikidesign foreach ($comment['replies'] as $rid) { 258c3413364SGerrit Uitslag $this->addComment($rid, $data, $result, $cid, $level + 1); 259573e23a1Swikidesign } 260573e23a1Swikidesign } 261264b7327Swikidesign } 262264b7327Swikidesign 263264b7327Swikidesign /** 264c3413364SGerrit Uitslag * Returns html of checkbox and info about a comment item 265e7ac9adaSGerrit Uitslag * 266c3413364SGerrit Uitslag * @param array $comment array with comment data 267c3413364SGerrit Uitslag * @return string html of checkbox and info 268264b7327Swikidesign */ 269*9699b3e2SGerrit Uitslag public function commentItem($comment) 270*9699b3e2SGerrit Uitslag { 271264b7327Swikidesign global $conf; 272264b7327Swikidesign 273264b7327Swikidesign // prepare variables 274264b7327Swikidesign if (is_array($comment['user'])) { // new format 275264b7327Swikidesign $name = $comment['user']['name']; 276264b7327Swikidesign $mail = $comment['user']['mail']; 277264b7327Swikidesign } else { // old format 278264b7327Swikidesign $name = $comment['name']; 279264b7327Swikidesign $mail = $comment['mail']; 280264b7327Swikidesign } 281264b7327Swikidesign if (is_array($comment['date'])) { // new format 282264b7327Swikidesign $created = $comment['date']['created']; 283264b7327Swikidesign } else { // old format 284264b7327Swikidesign $created = $comment['date']; 285264b7327Swikidesign } 286573e23a1Swikidesign $abstract = preg_replace('/\s+?/', ' ', strip_tags($comment['xhtml'])); 287c3413364SGerrit Uitslag if (PhpString::strlen($abstract) > 160) { 288c3413364SGerrit Uitslag $abstract = PhpString::substr($abstract, 0, 160) . '...'; 2894cded5e1SGerrit Uitslag } 290264b7327Swikidesign 291573e23a1Swikidesign return '<input type="checkbox" name="cid[' . $comment['id'] . ']" value="1" /> ' . 292f014bc86SMichael Klier $this->email($mail, $name, 'email') . ', ' . strftime($conf['dformat'], $created) . ': ' . 293573e23a1Swikidesign '<span class="abstract">' . $abstract . '</span>'; 294264b7327Swikidesign } 295264b7327Swikidesign 296264b7327Swikidesign /** 297c3413364SGerrit Uitslag * Returns html of list item openings tag 298e7ac9adaSGerrit Uitslag * 299e7ac9adaSGerrit Uitslag * @param array $comment 300e7ac9adaSGerrit Uitslag * @return string 301264b7327Swikidesign */ 302*9699b3e2SGerrit Uitslag public function liComment($comment) 303*9699b3e2SGerrit Uitslag { 304c3413364SGerrit Uitslag $showclass = ($comment['show'] ? '' : ' hidden'); 305c3413364SGerrit Uitslag return '<li class="level' . $comment['level'] . $showclass . '">'; 306264b7327Swikidesign } 307264b7327Swikidesign 308264b7327Swikidesign /** 309264b7327Swikidesign * Show buttons to bulk remove, hide or show comments 310264b7327Swikidesign */ 311*9699b3e2SGerrit Uitslag protected function actionButtons() 312*9699b3e2SGerrit Uitslag { 313264b7327Swikidesign global $lang; 314264b7327Swikidesign 3156fd05bf5Swikidesign ptln('<div class="comment_buttons">', 12); 3166fd05bf5Swikidesign ptln('<input type="submit" name="comment" value="' . $this->getLang('btn_show') . '" class="button" title="' . $this->getLang('btn_show') . '" />', 14); 3176fd05bf5Swikidesign ptln('<input type="submit" name="comment" value="' . $this->getLang('btn_hide') . '" class="button" title="' . $this->getLang('btn_hide') . '" />', 14); 3186fd05bf5Swikidesign ptln('<input type="submit" name="comment" value="' . $lang['btn_delete'] . '" class="button" title="' . $lang['btn_delete'] . '" />', 14); 3196fd05bf5Swikidesign ptln('</div>', 12); // class="comment_buttons" 3206fd05bf5Swikidesign ptln('</div>', 10); // class="no" 3216fd05bf5Swikidesign ptln('</form>', 8); 3226fd05bf5Swikidesign ptln('</div>', 6); // class="level2" 323573e23a1Swikidesign } 324573e23a1Swikidesign 325573e23a1Swikidesign /** 326573e23a1Swikidesign * Displays links to older newer discussions 327e7ac9adaSGerrit Uitslag * 328c3413364SGerrit Uitslag * @param bool $isMore whether there are more pages needed 329c3413364SGerrit Uitslag * @param int $first first entry on this page 330c3413364SGerrit Uitslag * @param int $num number of entries per page 331573e23a1Swikidesign */ 332*9699b3e2SGerrit Uitslag protected function browseDiscussionLinks($isMore, $first, $num) 333*9699b3e2SGerrit Uitslag { 334573e23a1Swikidesign global $ID; 335573e23a1Swikidesign 336c3413364SGerrit Uitslag if ($first == 0 && !$isMore) return; 3373e02b3ffSwikidesign 338c3413364SGerrit Uitslag $params = ['do' => 'admin', 'page' => 'discussion']; 339573e23a1Swikidesign $last = $first + $num; 3406fd05bf5Swikidesign ptln('<div class="level1">', 8); 341c3413364SGerrit Uitslag $return = ''; 342573e23a1Swikidesign if ($first > 0) { 343573e23a1Swikidesign $first -= $num; 3444cded5e1SGerrit Uitslag if ($first < 0) { 3454cded5e1SGerrit Uitslag $first = 0; 3464cded5e1SGerrit Uitslag } 347573e23a1Swikidesign $params['first'] = $first; 3486fd05bf5Swikidesign ptln('<p class="centeralign">', 8); 349c3413364SGerrit Uitslag $return = '<a href="' . wl($ID, $params) . '" class="wikilink1"><< ' . $this->getLang('newer') . '</a>'; 350c3413364SGerrit Uitslag if ($isMore) { 351c3413364SGerrit Uitslag $return .= ' | '; 352573e23a1Swikidesign } else { 353c3413364SGerrit Uitslag ptln($return, 10); 3546fd05bf5Swikidesign ptln('</p>', 8); 355573e23a1Swikidesign } 356c3413364SGerrit Uitslag } elseif ($isMore) { 3576fd05bf5Swikidesign ptln('<p class="centeralign">', 8); 358573e23a1Swikidesign } 359c3413364SGerrit Uitslag if ($isMore) { 360573e23a1Swikidesign $params['first'] = $last; 361c3413364SGerrit Uitslag $return .= '<a href="' . wl($ID, $params) . '" class="wikilink1">' . $this->getLang('older') . ' >></a>'; 362c3413364SGerrit Uitslag ptln($return, 10); 3636fd05bf5Swikidesign ptln('</p>', 8); 364573e23a1Swikidesign } 3656fd05bf5Swikidesign ptln('</div>', 6); // class="level1" 366573e23a1Swikidesign } 367573e23a1Swikidesign 368573e23a1Swikidesign /** 369c3413364SGerrit Uitslag * Changes the status of a comment section 370e7ac9adaSGerrit Uitslag * 371c3413364SGerrit Uitslag * @param int $new 0=disabled, 1=enabled, 2=closed 372573e23a1Swikidesign */ 373*9699b3e2SGerrit Uitslag protected function changeStatus($new) 374*9699b3e2SGerrit Uitslag { 375573e23a1Swikidesign global $ID; 376573e23a1Swikidesign 377573e23a1Swikidesign // get discussion meta file name 378573e23a1Swikidesign $file = metaFN($ID, '.comments'); 379573e23a1Swikidesign $data = unserialize(io_readFile($file, false)); 380573e23a1Swikidesign 381573e23a1Swikidesign $old = $data['status']; 382c3413364SGerrit Uitslag if ($old == $new) { 383c3413364SGerrit Uitslag return; 384c3413364SGerrit Uitslag } 385573e23a1Swikidesign 386573e23a1Swikidesign // save the comment metadata file 387573e23a1Swikidesign $data['status'] = $new; 388573e23a1Swikidesign io_saveFile($file, serialize($data)); 389573e23a1Swikidesign 390573e23a1Swikidesign // look for ~~DISCUSSION~~ command in page file and change it accordingly 391c3413364SGerrit Uitslag $patterns = ['~~DISCUSSION:off\2~~', '~~DISCUSSION\2~~', '~~DISCUSSION:closed\2~~']; 392573e23a1Swikidesign $replace = $patterns[$new]; 3933e02b3ffSwikidesign $wiki = preg_replace('/~~DISCUSSION([\w:]*)(\|?.*?)~~/', $replace, rawWiki($ID)); 394573e23a1Swikidesign saveWikiText($ID, $wiki, $this->getLang('statuschanged'), true); 395264b7327Swikidesign } 3965ef1705fSiLoveiDo} 397