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