1<?php 2/** 3 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 4 * @author Esther Brunner <wikidesign@gmail.com> 5 */ 6 7class admin_plugin_linkback extends DokuWiki_Admin_Plugin { 8 9 function getMenuSort(){ return 201; } 10 function forAdminOnly(){ return false; } 11 12 function handle(){ 13 global $lang; 14 15 $lid = $_REQUEST['lid']; 16 if (is_array($lid)) $lid = array_keys($lid); 17 18 /** @var action_plugin_linkback_display $action */ 19 $action = plugin_load('action', 'linkback_display'); 20 if (!$action) return; // couldn't load action plugin component 21 22 switch ($_REQUEST['linkback']){ 23 case $lang['btn_delete']: 24 $action->_deleteLinkbacks($lid); 25 break; 26 27 case $this->getLang('btn_show'): 28 $action->_changeLinkbackVisibilities($lid, true); 29 break; 30 31 case $this->getLang('btn_hide'): 32 $action->_changeLinkbackVisibilities($lid, false); 33 break; 34 35 case $this->getLang('btn_ham'): 36 $action->_markLinkbacks($lid, false); 37 break; 38 39 case $this->getLang('btn_spam'): 40 $action->_markLinkbacks($lid, true); 41 break; 42 43 case $this->getLang('btn_change'): 44 $this->_changeStatus($_REQUEST['status']); 45 break; 46 } 47 } 48 49 function html(){ 50 global $conf; 51 52 $first = $_REQUEST['first']; 53 if (!is_numeric($first)) $first = 0; 54 $num = $conf['recent']; 55 56 ptln('<h1>'.$this->getLang('menu').'</h1>'); 57 58 $targets = $this->_getTargets(); 59 60 // slice the needed chunk of linkback targets 61 $more = count($targets) > ($first + $num); 62 $targets = array_slice($targets, $first, $num); 63 64 foreach ($targets as $target){ 65 $linkbacks = $this->_getLinkbacks($target); 66 $this->_targetHead($target); 67 if ($linkbacks === false){ 68 ptln('</div>', 6); // class="level2" 69 continue; 70 } 71 72 ptln('<form method="post" action="'.wl($target['id']).'">', 8); 73 ptln('<div class="no">', 10); 74 ptln('<input type="hidden" name="do" value="admin" />', 10); 75 ptln('<input type="hidden" name="page" value="linkback" />', 10); 76 echo html_buildlist($linkbacks, 'admin_linkback', array($this, '_linkbackItem'), array($this, '_li_linkback')); 77 $this->_actionButtons($target['id']); 78 } 79 80 $this->_browseLinkbackLinks($more, $first, $num); 81 82 if(count($targets) === 0) { 83 echo 'No linkback targets'; 84 } 85 } 86 87 /** 88 * Returns an array of targets with linkback features, sorted by recent linkbacks 89 */ 90 function _getTargets(){ 91 global $conf; 92 93 // returns the list of pages in the given namespace and it's subspaces 94 $items = array(); 95 search($items, $conf['datadir'], 'search_allpages', array()); 96 97 // add pages with comments to result 98 $result = array(); 99 foreach ($items as $item){ 100 $id = $item['id']; 101 102 // some checks 103 $file = metaFN($id, '.linkbacks'); 104 if (!@file_exists($file)) continue; // skip if no comments file 105 106 $date = filemtime($file); 107 $result[] = array( 108 'id' => $id, 109 'file' => $file, 110 'date' => $date, 111 ); 112 } 113 114 // finally sort by time of last comment 115 usort($result, array('admin_plugin_linkback', '_targetCmp')); 116 117 return $result; 118 } 119 120 /** 121 * Callback for comparison of target data. 122 * 123 * Used for sorting targets in descending order by date of last linkback. 124 * If this date happens to be equal for the compared targest, page id 125 * is used as second comparison attribute. 126 */ 127 function _targetCmp($a, $b) { 128 if ($a['date'] == $b['date']) { 129 return strcmp($a['id'], $b['id']); 130 } 131 return ($a['date'] < $b['date']) ? 1 : -1; 132 } 133 134 /** 135 * Outputs header, page ID and status of linkbacks 136 */ 137 function _targetHead($target){ 138 $id = $target['id']; 139 140 $labels = array( 141 'send' => $this->getLang('send'), 142 'receive' => $this->getLang('receive'), 143 'display' => $this->getLang('display') 144 ); 145 $title = p_get_metadata($id, 'title'); 146 if (!$title) $title = $id; 147 ptln('<h2 name="'.$id.'" id="'.$id.'">'.hsc($title).'</h2>', 6); 148 ptln('<form method="post" action="'.wl($id).'">', 6); 149 ptln('<div class="mediaright">', 8); 150 ptln('<input type="hidden" name="do" value="admin" />', 10); 151 ptln('<input type="hidden" name="page" value="linkback" />', 10); 152 ptln($this->getLang('status').': ', 10); 153 foreach ($labels as $key => $label){ 154 $selected = (($target[$key]) ? ' checked="checked"' : ''); 155 ptln('<label for="status_'.$id.'_'.$key.'">'.$label.'</label> <input type="checkbox" name="status['.$key.']" value="1"'.$selected.' id="status_'.$id.'_'.$key.'" /> ', 12); 156 } 157 ptln('<input type="submit" name="linkback" value="'.$this->getLang('btn_change').'" class"button" title="'.$this->getLang('btn_change').'" />', 10); 158 ptln('</div>', 8); 159 ptln('</form>', 6); 160 ptln('<div class="level2">', 6); 161 ptln('<a href="'.wl($id).'" class="wikilink1">'.$id.'</a> ', 8); 162 return true; 163 } 164 165 /** 166 * Returns the full comments data for a given wiki page 167 */ 168 function _getLinkbacks(&$target){ 169 $id = $target['id']; 170 171 if (!$target['file']) $target['file'] = metaFN($id, '.linkbacks'); 172 if (!@file_exists($target['file'])) return false; // no discussion thread at all 173 174 $data = unserialize(io_readFile($target['file'], false)); 175 176 $target['send'] = $data['send']; 177 $target['receive'] = $data['receive']; 178 $target['display'] = $data['display']; 179 $target['number'] = $data['number']; 180 181 if (!$data['display']) return false; // comments are turned off 182 if (!$data['receivedpings']) return false; // no comments 183 184 $result = array(); 185 foreach($data['receivedpings'] as $lid => $linkback) { 186 $linkback['level'] = 1; 187 $result[$lid] = $linkback; 188 } 189 190 if (empty($result)) return false; 191 else return $result; 192 } 193 194 /** 195 * Checkbox and info about a linkback item 196 */ 197 function _linkbackItem($linkback){ 198 global $conf; 199 200 // prepare variables 201 $title = $linkback['title']; 202 $url = $linkback['url']; 203 $date = $linkback['received']; 204 $excerpt = $linkback['excerpt']; 205 $type = $linkback['type']; 206 207 if (utf8_strlen($excerpt) > 160) $excerpt = utf8_substr($excerpt, 0, 160).'...'; 208 209 return '<input type="checkbox" name="lid['.$linkback['lid'].']" value="1" /> '. 210 $this->external_link($url, $title).', '.strftime($conf['dformat'], $date).', ' . $this->getLang('linkback_type_' . $type) . ': '. 211 '<span class="excerpt">'.$excerpt.'</span>'; 212 } 213 214 /** 215 * list item tag 216 */ 217 function _li_linkback($linkback){ 218 if (!$linkback['show']) 219 return '<li class="hidden">'; 220 return '<li>'; 221 } 222 223 /** 224 * Show buttons to bulk remove, hide or show comments 225 */ 226 function _actionButtons($id){ 227 ptln('<div class="linkback_buttons">', 12); 228 ptln('<input type="submit" name="linkback" value="'.$this->getLang('btn_show').'" class="button" title="'.$this->getLang('btn_show').'" />', 14); 229 ptln('<input type="submit" name="linkback" value="'.$this->getLang('btn_hide').'" class="button" title="'.$this->getLang('btn_hide').'" />', 14); 230 ptln('<input type="submit" name="linkback" value="'.$this->getLang('btn_ham').'" class="button" title="'.$this->getLang('btn_ham').'" />', 14); 231 ptln('<input type="submit" name="linkback" value="'.$this->getLang('btn_spam').'" class="button" title="'.$this->getLang('btn_spam').'" />', 14); 232 ptln('<input type="submit" name="linkback" value="'.$this->getLang('btn_delete').'" class="button" title="'.$this->getLang('btn_delete').'" />', 14); 233 ptln('</div>', 12); // class="comment_buttons" 234 ptln('</div>', 10); // class="no" 235 ptln('</form>', 8); 236 ptln('</div>', 6); // class="level2" 237 return true; 238 } 239 240 /** 241 * Displays links to older newer discussions 242 */ 243 function _browseLinkbackLinks($more, $first, $num){ 244 global $ID; 245 246 if (($first == 0) && (!$more)) return true; 247 248 $params = array('do' => 'admin', 'page' => 'linkback'); 249 $last = $first+$num; 250 ptln('<div class="level1">', 8); 251 if ($first > 0){ 252 $first -= $num; 253 if ($first < 0) $first = 0; 254 $params['first'] = $first; 255 ptln('<p class="centeralign">', 8); 256 $ret = '<a href="'.wl($ID, $params).'" class="wikilink1"><< '.$this->getLang('newer').'</a>'; 257 if ($more){ 258 $ret .= ' | '; 259 } else { 260 ptln($ret, 10); 261 ptln('</p>', 8); 262 } 263 } else if ($more){ 264 ptln('<p class="centeralign">', 8); 265 } 266 if ($more){ 267 $params['first'] = $last; 268 $ret .= '<a href="'.wl($ID, $params).'" class="wikilink1">'.$this->getLang('older').' >></a>'; 269 ptln($ret, 10); 270 ptln('</p>', 8); 271 } 272 ptln('</div>', 6); // class="level1" 273 return true; 274 } 275 276 /** 277 * Changes the status of a comment 278 */ 279 function _changeStatus($new){ 280 global $ID; 281 282 // get discussion meta file name 283 $file = metaFN($ID, '.linkbacks'); 284 $data = unserialize(io_readFile($file, false)); 285 286 $updated = false; 287 $updateText = false; 288 foreach (array('send', 'receive', 'display') as $key) { 289 if (!isset($new[$key])) 290 $new[$key] = false; 291 292 if ($new[$key] == $data[$key]) 293 continue; 294 295 $data[$key] = $new[$key]; 296 $updated = true; 297 298 if (in_array($key, array('send', 'receive'))) 299 $updateText = true; 300 } 301 302 if (!$updated) 303 return false; 304 305 // save the comment metadata file 306 io_saveFile($file, serialize($data)); 307 308 if (!$updateText) 309 return true; 310 311 // look for ~~LINKBACK~~ command in page file and change it accordingly 312 if ($data['receive'] && $data['display']) 313 $replace = '~~LINKBACK~~'; 314 else if (!$data['receive']) 315 $replace = '~~LINKBACK:closed~~'; 316 else 317 $replace = '~~LINKBACK:off~~'; 318 $wiki = preg_replace('/~~LINKBACK([\w:]*)~~/', $replace, rawWiki($ID)); 319 saveWikiText($ID, $wiki, $this->getLang('statuschanged'), true); 320 321 return true; 322 } 323 324} 325 326//Setup VIM: ex: et ts=4 enc=utf-8 : 327